main_interface.cpp 58 KB
Newer Older
Clément Stenac's avatar
Clément Stenac committed
1
/*****************************************************************************
Clément Stenac's avatar
Clément Stenac committed
2
 * main_interface.cpp : Main interface
Clément Stenac's avatar
Clément Stenac committed
3
 ****************************************************************************
4
 * Copyright (C) 2006-2011 VideoLAN and AUTHORS
5
 * $Id$
Clément Stenac's avatar
Clément Stenac committed
6 7
 *
 * Authors: Clément Stenac <zorglub@videolan.org>
8
 *          Jean-Baptiste Kempf <jb@videolan.org>
9
 *          Ilkka Ollakka <ileoo@videolan.org>
Clément Stenac's avatar
Clément Stenac committed
10 11 12 13 14 15 16 17 18 19 20 21
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
22 23
 * along with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24
 *****************************************************************************/
25

26 27 28
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
Clément Stenac's avatar
Clément Stenac committed
29

30
#include "qt.hpp"
31

Clément Stenac's avatar
Clément Stenac committed
32
#include "main_interface.hpp"
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
33 34
#include "input_manager.hpp"                    // Creation
#include "actions_manager.hpp"                  // killInstance
35

36
#include "util/customwidgets.hpp"               // qtEventToVLCKey, QVLCStackedWidget
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
37
#include "util/qt_dirs.hpp"                     // toNativeSeparators
38
#include "util/imagehelper.hpp"
39

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
40 41 42 43
#include "components/interface_widgets.hpp"     // bgWidget, videoWidget
#include "components/controller.hpp"            // controllers
#include "components/playlist/playlist.hpp"     // plWidget
#include "dialogs/firstrun.hpp"                 // First Run
44
#include "dialogs/playlist.hpp"                 // PlaylistDialog
45

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
46 47
#include "menus.hpp"                            // Menu creation
#include "recents.hpp"                          // RecentItems when DnD
48

Clément Stenac's avatar
Clément Stenac committed
49
#include <QCloseEvent>
50
#include <QKeyEvent>
51

52
#include <QUrl>
53
#include <QSize>
54
#include <QDate>
55
#include <QMimeData>
56

57
#include <QWindow>
58
#include <QMenu>
59 60
#include <QMenuBar>
#include <QStatusBar>
61
#include <QLabel>
62
#include <QStackedWidget>
63
#include <QScreen>
64
#ifdef _WIN32
65
#include <QFileInfo>
66
#endif
67

68 69 70 71 72
#if ! HAS_QT510 && defined(QT5_HAS_X11)
# include <QX11Info>
# include <X11/Xlib.h>
#endif

73 74
#include <QTimer>

75
#include <vlc_actions.h>                    /* Wheel event */
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
76
#include <vlc_vout_display.h>               /* vout_thread_t and VOUT_ events */
77

78
// #define DEBUG_INTF
79

80 81 82 83 84
/* Callback prototypes */
static int PopupMenuCB( vlc_object_t *p_this, const char *psz_variable,
                        vlc_value_t old_val, vlc_value_t new_val, void *param );
static int IntfShowCB( vlc_object_t *p_this, const char *psz_variable,
                       vlc_value_t old_val, vlc_value_t new_val, void *param );
85 86
static int IntfBossCB( vlc_object_t *p_this, const char *psz_variable,
                       vlc_value_t old_val, vlc_value_t new_val, void *param );
87 88 89
static int IntfRaiseMainCB( vlc_object_t *p_this, const char *psz_variable,
                           vlc_value_t old_val, vlc_value_t new_val,
                           void *param );
90

91 92 93
const QEvent::Type MainInterface::ToolbarsNeedRebuild =
        (QEvent::Type)QEvent::registerEventType();

94
MainInterface::MainInterface( intf_thread_t *_p_intf ) : QVLCMW( _p_intf )
Clément Stenac's avatar
Clément Stenac committed
95
{
96
    /* Variables initialisation */
97 98 99
    bgWidget             = NULL;
    videoWidget          = NULL;
    playlistWidget       = NULL;
100
    stackCentralOldWidget= NULL;
101
    lastWinScreen        = NULL;
102 103
    sysTray              = NULL;
    fullscreenControls   = NULL;
104
    cryptedLabel         = NULL;
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
105 106
    controls             = NULL;
    inputC               = NULL;
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
107

108
    b_hideAfterCreation  = false; // --qt-start-minimized
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
109
    playlistVisible      = false;
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
110
    input_name           = "";
111
    b_interfaceFullScreen= false;
112
    b_hasPausedWhenMinimized = false;
113
    i_kc_offset          = false;
114
    b_maximizedView      = false;
115
    b_isWindowTiled      = false;
116

117
    /* Ask for Privacy */
118
    FirstRun::CheckAndRun( this, p_intf );
119

120
    /**
121
     *  Configuration and settings
122
     *  Pre-building of interface
123
     **/
124 125
    /* Main settings */
    setFocusPolicy( Qt::StrongFocus );
126
    setAcceptDrops( true );
Nick Pope's avatar
Nick Pope committed
127
    setWindowRole( "vlc-main" );
128
    setWindowIcon( QApplication::windowIcon() );
129
    setWindowOpacity( var_InheritFloat( p_intf, "qt-opacity" ) );
130

131
    /* Does the interface resize to video size or the opposite */
132
    b_autoresize = var_InheritBool( p_intf, "qt-video-autoresize" );
133

134
    /* Are we in the enhanced always-video mode or not ? */
135
    b_minimalView = var_InheritBool( p_intf, "qt-minimal-view" );
Clément Stenac's avatar
Clément Stenac committed
136

137
    /* Do we want anoying popups or not */
138
    i_notificationSetting = var_InheritInteger( p_intf, "qt-notification" );
139

140
    /* */
141 142
    b_pauseOnMinimize = var_InheritBool( p_intf, "qt-pause-minimized" );

143
    /* Set the other interface settings */
144
    settings = getSettings();
145

146
    /* */
147
    b_plDocked = getSettings()->value( "MainWindow/pl-dock-status", true ).toBool();
148

149 150
    /* Should the UI stays on top of other windows */
    b_interfaceOnTop = var_InheritBool( p_intf, "video-on-top" );
151

152 153 154 155 156
#ifdef QT5_HAS_WAYLAND
    b_hasWayland = QGuiApplication::platformName()
        .startsWith(QLatin1String("wayland"), Qt::CaseInsensitive);
#endif

157 158 159
    /**************************
     *  UI and Widgets design
     **************************/
160
    setVLCWindowsTitle();
161

162 163 164
    /************
     * Menu Bar *
     ************/
165
    VLCMenuBar::createMenuBar( this, p_intf );
166
    CONNECT( THEMIM->getIM(), voutListChanged( vout_thread_t **, int ),
167
             THEDP, destroyPopupMenu() );
168

169
    createMainWidget( settings );
170 171 172 173 174 175 176

    /**************
     * Status Bar *
     **************/
    createStatusBar();
    setStatusBarVisibility( getSettings()->value( "MainWindow/status-bar-visible", false ).toBool() );

177 178 179 180 181
    /*********************************
     * Create the Systray Management *
     *********************************/
    initSystray();

182 183 184 185
    /*************************************************************
     * Connect the input manager to the GUI elements it manages  *
     * Beware initSystray did some connects on input manager too *
     *************************************************************/
186 187
    /**
     * Connects on nameChanged()
188 189 190
     * Those connects are different because options can impeach them to trigger.
     **/
    /* Main Interface statusbar */
191 192
    CONNECT( THEMIM->getIM(), nameChanged( const QString& ),
             this, setName( const QString& ) );
193
    /* and title of the Main Interface*/
194
    if( var_InheritBool( p_intf, "qt-name-in-title" ) )
195
    {
196 197
        CONNECT( THEMIM->getIM(), nameChanged( const QString& ),
                 this, setVLCWindowsTitle( const QString& ) );
198
    }
199 200
    CONNECT( THEMIM, inputChanged( bool ), this, onInputChanged( bool ) );

201 202
    /* END CONNECTS ON IM */

203
    /* VideoWidget connects for asynchronous calls */
204
    b_videoFullScreen = false;
205 206
    connect( this, SIGNAL(askGetVideo(struct vout_window_t*, unsigned, unsigned, bool, bool*)),
             this, SLOT(getVideoSlot(struct vout_window_t*, unsigned, unsigned, bool, bool*)),
207
             Qt::BlockingQueuedConnection );
208
    connect( this, SIGNAL(askReleaseVideo( void )),
209 210
             this, SLOT(releaseVideoSlot( void )),
             Qt::BlockingQueuedConnection );
211
    CONNECT( this, askVideoOnTop(bool), this, setVideoOnTop(bool));
212

213
    if( videoWidget )
214
    {
215 216 217
        if( b_autoresize )
        {
            CONNECT( videoWidget, sizeChanged( int, int ),
218
                     this, videoSizeChanged( int,  int ) );
219
        }
220 221 222
        CONNECT( this, askVideoToResize( unsigned int, unsigned int ),
                 this, setVideoSize( unsigned int, unsigned int ) );

223
        CONNECT( this, askVideoSetFullScreen( bool ),
224
                 this, setVideoFullScreen( bool ) );
225 226
        CONNECT( this, askHideMouse( bool ),
                 this, setHideMouse( bool ) );
227 228
    }

229 230
    CONNECT( THEDP, toolBarConfUpdated(), this, toolBarConfUpdated() );
    installEventFilter( this );
231

232 233
    CONNECT( this, askToQuit(), THEDP, quit() );

234
    CONNECT( this, askBoss(), this, setBoss() );
235
    CONNECT( this, askRaise(), this, setRaise() );
236

237 238

    connect( THEDP, &DialogsProvider::releaseMouseEvents, this, &MainInterface::voutReleaseMouseEvents ) ;
239 240 241
    /** END of CONNECTS**/


242 243 244
    /************
     * Callbacks
     ************/
245 246 247
    var_AddCallback( p_intf->obj.libvlc, "intf-toggle-fscontrol", IntfShowCB, p_intf );
    var_AddCallback( p_intf->obj.libvlc, "intf-boss", IntfBossCB, p_intf );
    var_AddCallback( p_intf->obj.libvlc, "intf-show", IntfRaiseMainCB, p_intf );
248 249

    /* Register callback for the intf-popupmenu variable */
250
    var_AddCallback( p_intf->obj.libvlc, "intf-popupmenu", PopupMenuCB, p_intf );
251

252

253
    /* Final Sizing, restoration and placement of the interface */
254
    if( settings->value( "MainWindow/playlist-visible", false ).toBool() )
255 256
        togglePlaylist();

257
    QVLCTools::restoreWidgetPosition( settings, this, QSize(600, 420) );
258

259 260
    b_interfaceFullScreen = isFullScreen();

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
261
    setVisible( !b_hideAfterCreation );
262

263
    /* Switch to minimal view if needed, must be called after the show() */
264
    if( b_minimalView )
265
        toggleMinimalView( true );
266 267

    computeMinimumSize();
268 269 270 271
}

MainInterface::~MainInterface()
{
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
272
    /* Unsure we hide the videoWidget before destroying it */
273
    if( stackCentralOldWidget == videoWidget )
274
        showTab( bgWidget );
275

276 277
    if( videoWidget )
        releaseVideoSlot();
278

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
279
    /* Be sure to kill the actionsManager... Only used in the MI and control */
280 281
    ActionsManager::killInstance();

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
282
    /* Delete the FSC controller */
283
    delete fullscreenControls;
284

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
285
    /* Save states */
286

287
    settings->beginGroup("MainWindow");
288
    settings->setValue( "pl-dock-status", b_plDocked );
289

290
    /* Save playlist state */
291
    settings->setValue( "playlist-visible", playlistVisible );
292

293
    settings->setValue( "adv-controls",
294
                        getControlsVisibilityStatus() & CONTROLS_ADVANCED );
295
    settings->setValue( "status-bar-visible", b_statusbarVisible );
296

297 298 299
    /* Save the stackCentralW sizes */
    settings->setValue( "bgSize", stackWidgetsSizes[bgWidget] );
    settings->setValue( "playlistSize", stackWidgetsSizes[playlistWidget] );
300
    settings->endGroup();
301

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
302
    /* Save this size */
303
    QVLCTools::saveWidgetPosition(settings, this);
304

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
305
    /* Unregister callbacks */
306 307 308 309
    var_DelCallback( p_intf->obj.libvlc, "intf-boss", IntfBossCB, p_intf );
    var_DelCallback( p_intf->obj.libvlc, "intf-show", IntfRaiseMainCB, p_intf );
    var_DelCallback( p_intf->obj.libvlc, "intf-toggle-fscontrol", IntfShowCB, p_intf );
    var_DelCallback( p_intf->obj.libvlc, "intf-popupmenu", PopupMenuCB, p_intf );
310

311
    p_intf->p_sys->p_mi = NULL;
312 313
}

314 315
void MainInterface::computeMinimumSize()
{
316
    int minWidth = 80;
317
    if( menuBar()->isVisible() )
318
        minWidth += controls->sizeHint().width();
319 320 321 322

    setMinimumWidth( minWidth );
}

323 324 325
/*****************************
 *   Main UI handling        *
 *****************************/
326 327
void MainInterface::recreateToolbars()
{
328 329
    bool b_adv = getControlsVisibilityStatus() & CONTROLS_ADVANCED;

330 331
    delete controls;
    delete inputC;
332

333
    controls = new ControlsWidget( p_intf, b_adv, this );
334
    inputC = new InputControlsWidget( p_intf, this );
335
    mainLayout->insertWidget( 2, inputC );
336
    mainLayout->insertWidget( settings->value( "MainWindow/ToolbarPos", false ).toBool() ? 0: 3,
337
                              controls );
338

339 340 341 342 343 344
    if( fullscreenControls )
    {
        delete fullscreenControls;
        fullscreenControls = new FullscreenControllerWidget( p_intf, this );
        CONNECT( fullscreenControls, keyPressed( QKeyEvent * ),
                 this, handleKeyPress( QKeyEvent * ) );
345
        THEMIM->requestVoutUpdate();
346
    }
347 348

    setMinimalView( b_minimalView );
349 350
}

351 352
void MainInterface::reloadPrefs()
{
353
    i_notificationSetting = var_InheritInteger( p_intf, "qt-notification" );
354 355 356 357 358 359 360 361
    b_pauseOnMinimize = var_InheritBool( p_intf, "qt-pause-minimized" );
    if( !var_InheritBool( p_intf, "qt-fs-controller" ) && fullscreenControls )
    {
        delete fullscreenControls;
        fullscreenControls = NULL;
    }
}

362
void MainInterface::createResumePanel( QWidget *w )
363
{
364 365 366 367
    resumePanel = new QWidget( w );
    resumePanel->hide();
    QHBoxLayout *resumePanelLayout = new QHBoxLayout( resumePanel );
    resumePanelLayout->setSpacing( 0 ); resumePanelLayout->setMargin( 0 );
368

369
    QLabel *continuePixmapLabel = new QLabel();
370
    continuePixmapLabel->setPixmap( ImageHelper::loadSvgToPixmap( ":/menu/help.svg" , fontMetrics().height(), fontMetrics().height()) );
371 372
    continuePixmapLabel->setContentsMargins( 5, 0, 5, 0 );

373
    QLabel *continueLabel = new QLabel( qtr( "Do you want to restart the playback where left off?") );
374

375
    QToolButton *cancel = new QToolButton( resumePanel );
376 377
    cancel->setAutoRaise( true );
    cancel->setText( "X" );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
378 379

    QPushButton *ok = new QPushButton( qtr( "&Continue" )  );
380

381
    resumePanelLayout->addWidget( continuePixmapLabel );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
382
    resumePanelLayout->addWidget( continueLabel );
383 384 385
    resumePanelLayout->addStretch( 1 );
    resumePanelLayout->addWidget( ok );
    resumePanelLayout->addWidget( cancel );
386

387 388 389
    resumeTimer = new QTimer( resumePanel );
    resumeTimer->setSingleShot( true );
    resumeTimer->setInterval( 6000 );
390

391 392 393
    CONNECT( resumeTimer, timeout(), this, hideResumePanel() );
    CONNECT( cancel, clicked(), this, hideResumePanel() );
    CONNECT( THEMIM->getIM(), resumePlayback(int64_t), this, showResumePanel(int64_t) );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
394
    BUTTONACT( ok, resumePlayback() );
395

396
    w->layout()->addWidget( resumePanel );
397 398
}

399
void MainInterface::showResumePanel( int64_t _time ) {
400 401 402 403 404
    int setting = var_InheritInteger( p_intf, "qt-continue" );

    if( setting == 0 )
        return;

405
    i_resumeTime = _time;
406 407

    if( setting == 2)
408
        resumePlayback();
409 410
    else
    {
411
        if( !isFullScreen() && !isMaximized() && !b_isWindowTiled )
412
            resizeWindow( width(), height() + resumePanel->height() );
413 414
        resumePanel->setVisible(true);
        resumeTimer->start();
415 416 417
    }
}

418
void MainInterface::hideResumePanel()
419
{
420
    if( resumePanel->isVisible() )
421
    {
422
        if( !isFullScreen() && !isMaximized() && !b_isWindowTiled )
423
            resizeWindow( width(), height() - resumePanel->height() );
424 425
        resumePanel->hide();
        resumeTimer->stop();
426 427 428
    }
}

429
void MainInterface::resumePlayback()
430
{
431
    if( THEMIM->getIM()->hasInput() ) {
432
        var_SetInteger( THEMIM->getInput(), "time", i_resumeTime );
433
    }
434
    hideResumePanel();
435 436
}

437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453
void MainInterface::onInputChanged( bool hasInput )
{
    if( hasInput == false )
        return;
    int autoRaise = var_InheritInteger( p_intf, "qt-auto-raise" );
    if ( autoRaise == MainInterface::RAISE_NEVER )
        return;
    if( THEMIM->getIM()->hasVideo() == true )
    {
        if( ( autoRaise & MainInterface::RAISE_VIDEO ) == 0 )
            return;
    }
    else if ( ( autoRaise & MainInterface::RAISE_AUDIO ) == 0 )
        return;
    emit askRaise();
}

454
void MainInterface::createMainWidget( QSettings *creationSettings )
455
{
456
    /* Create the main Widget and the mainLayout */
457
    QWidget *main = new QWidget;
458
    setCentralWidget( main );
459
    mainLayout = new QVBoxLayout( main );
460
    main->setContentsMargins( 0, 0, 0, 0 );
461
    mainLayout->setSpacing( 0 ); mainLayout->setMargin( 0 );
462

463
    createResumePanel( main );
464
    /* */
465
    stackCentralW = new QVLCStackedWidget( main );
466

467
    /* Bg Cone */
468 469 470 471 472 473 474 475 476
    if ( QDate::currentDate().dayOfYear() >= QT_XMAS_JOKE_DAY
         && var_InheritBool( p_intf, "qt-icon-change" ) )
    {
        bgWidget = new EasterEggBackgroundWidget( p_intf );
        CONNECT( this, kc_pressed(), bgWidget, animate() );
    }
    else
        bgWidget = new BackgroundWidget( p_intf );

477
    stackCentralW->addWidget( bgWidget );
478 479 480 481 482
    if ( !var_InheritBool( p_intf, "qt-bgcone" ) )
        bgWidget->setWithArt( false );
    else
        if ( var_InheritBool( p_intf, "qt-bgcone-expands" ) )
            bgWidget->setExpandstoHeight( true );
483

484
    /* And video Outputs */
485
    if( var_InheritBool( p_intf, "embedded-video" ) )
486
    {
487
        videoWidget = new VideoWidget( p_intf, stackCentralW );
488
        stackCentralW->addWidget( videoWidget );
489
    }
490
    mainLayout->insertWidget( 1, stackCentralW );
491

492 493
    stackWidgetsSizes[bgWidget] =
        creationSettings->value( "MainWindow/bgSize", QSize( 600, 0 ) ).toSize();
494 495
    /* Resize even if no-auto-resize, because we are at creation */
    resizeStack( stackWidgetsSizes[bgWidget].width(), stackWidgetsSizes[bgWidget].height() );
Jakob Leben's avatar
Jakob Leben committed
496

497 498
    /* Create the CONTROLS Widget */
    controls = new ControlsWidget( p_intf,
499
        creationSettings->value( "MainWindow/adv-controls", false ).toBool(), this );
500 501
    inputC = new InputControlsWidget( p_intf, this );

502
    mainLayout->insertWidget( 2, inputC );
503
    mainLayout->insertWidget(
504
        creationSettings->value( "MainWindow/ToolbarPos", false ).toBool() ? 0: 3,
505
        controls );
506

507
    /* Visualisation, disabled for now, they SUCK */
508 509 510 511 512 513
    #if 0
    visualSelector = new VisualSelector( p_intf );
    mainLayout->insertWidget( 0, visualSelector );
    visualSelector->hide();
    #endif

514

515 516 517
    /* Enable the popup menu in the MI */
    main->setContextMenuPolicy( Qt::CustomContextMenu );
    CONNECT( main, customContextMenuRequested( const QPoint& ),
518
             THEDP, setPopupMenu() );
519

520 521 522 523 524 525 526 527
    if ( depth() > 8 ) /* 8bit depth has too many issues with opacity */
        /* Create the FULLSCREEN CONTROLS Widget */
        if( var_InheritBool( p_intf, "qt-fs-controller" ) )
        {
            fullscreenControls = new FullscreenControllerWidget( p_intf, this );
            CONNECT( fullscreenControls, keyPressed( QKeyEvent * ),
                     this, handleKeyPress( QKeyEvent * ) );
        }
528 529 530

    if ( b_interfaceOnTop )
        setWindowFlags( windowFlags() | Qt::WindowStaysOnTopHint );
Clément Stenac's avatar
Clément Stenac committed
531 532
}

533 534 535
inline void MainInterface::initSystray()
{
    bool b_systrayAvailable = QSystemTrayIcon::isSystemTrayAvailable();
536
    bool b_systrayWanted = var_InheritBool( p_intf, "qt-system-tray" );
537

538
    if( var_InheritBool( p_intf, "qt-start-minimized") )
539 540 541 542 543 544 545 546 547 548 549 550 551 552
    {
        if( b_systrayAvailable )
        {
            b_systrayWanted = true;
            b_hideAfterCreation = true;
        }
        else
            msg_Err( p_intf, "cannot start minimized without system tray bar" );
    }

    if( b_systrayAvailable && b_systrayWanted )
        createSystray();
}

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
553 554 555 556 557 558 559 560 561
inline void MainInterface::createStatusBar()
{
    /****************
     *  Status Bar  *
     ****************/
    /* Widgets Creation*/
    QStatusBar *statusBarr = statusBar();

    TimeLabel *timeLabel = new TimeLabel( p_intf );
562
    nameLabel = new ClickableQLabel();
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
563 564
    nameLabel->setTextInteractionFlags( Qt::TextSelectableByMouse
                                      | Qt::TextSelectableByKeyboard );
565
    SpeedLabel *speedLabel = new SpeedLabel( p_intf, this );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
566 567 568 569 570

    /* Styling those labels */
    timeLabel->setFrameStyle( QFrame::Sunken | QFrame::Panel );
    speedLabel->setFrameStyle( QFrame::Sunken | QFrame::Panel );
    nameLabel->setFrameStyle( QFrame::Sunken | QFrame::StyledPanel);
571 572 573 574
    timeLabel->setStyleSheet(
            "QLabel:hover { background-color: rgba(255, 255, 255, 50%) }" );
    speedLabel->setStyleSheet(
            "QLabel:hover { background-color: rgba(255, 255, 255, 50%) }" );
575 576
    /* pad both label and its tooltip */
    nameLabel->setStyleSheet( "padding-left: 5px; padding-right: 5px;" );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
577 578 579 580 581 582

    /* and adding those */
    statusBarr->addWidget( nameLabel, 8 );
    statusBarr->addPermanentWidget( speedLabel, 0 );
    statusBarr->addPermanentWidget( timeLabel, 0 );

583
    CONNECT( nameLabel, doubleClicked(), THEDP, epgDialog() );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
584 585 586 587
    /* timeLabel behaviour:
       - double clicking opens the goto time dialog
       - right-clicking and clicking just toggle between remaining and
         elapsed time.*/
588
    CONNECT( timeLabel, doubleClicked(), THEDP, gotoTimeDialog() );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
589 590 591

    CONNECT( THEMIM->getIM(), encryptionChanged( bool ),
             this, showCryptedLabel( bool ) );
592

593 594
    CONNECT( THEMIM->getIM(), seekRequested( float ),
             timeLabel, setDisplayPosition( float ) );
595 596 597 598 599 600 601 602

    /* This shouldn't be necessary, but for somehow reason, the statusBarr
       starts at height of 20px and when a text is shown it needs more space.
       But, as the QMainWindow policy doesn't allow statusBar to change QMW's
       geometry, we need to force a height. If you have a better idea, please
       tell me -- jb
     */
    statusBarr->setFixedHeight( statusBarr->sizeHint().height() + 2 );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
603 604
}

605
/**********************************************************************
606
 * Handling of sizing of the components
607
 **********************************************************************/
608

609 610
void MainInterface::debug()
{
611
#ifdef DEBUG_INTF
612 613 614 615 616 617
    if( controls ) {
        msg_Dbg( p_intf, "Controls size: %i - %i", controls->size().height(), controls->size().width() );
        msg_Dbg( p_intf, "Controls minimumsize: %i - %i", controls->minimumSize().height(), controls->minimumSize().width() );
        msg_Dbg( p_intf, "Controls sizeHint: %i - %i", controls->sizeHint().height(), controls->sizeHint().width() );
    }

618 619
    msg_Dbg( p_intf, "size: %i - %i", size().height(), size().width() );
    msg_Dbg( p_intf, "sizeHint: %i - %i", sizeHint().height(), sizeHint().width() );
620
    msg_Dbg( p_intf, "minimumsize: %i - %i", minimumSize().height(), minimumSize().width() );
621

622
    msg_Dbg( p_intf, "Stack size: %i - %i", stackCentralW->size().height(), stackCentralW->size().width() );
623
    msg_Dbg( p_intf, "Stack sizeHint: %i - %i", stackCentralW->sizeHint().height(), stackCentralW->sizeHint().width() );
624
    msg_Dbg( p_intf, "Central size: %i - %i", centralWidget()->size().height(), centralWidget()->size().width() );
625
#endif
626 627
}

628
inline void MainInterface::showVideo() { showTab( videoWidget ); }
629 630
inline void MainInterface::restoreStackOldWidget()
            { showTab( stackCentralOldWidget ); }
631 632

inline void MainInterface::showTab( QWidget *widget )
633
{
634
    if ( !widget ) widget = bgWidget; /* trying to restore a null oldwidget */
635
#ifdef DEBUG_INTF
636 637 638 639 640
    if ( stackCentralOldWidget )
        msg_Dbg( p_intf, "Old stackCentralOldWidget %s at index %i",
                 stackCentralOldWidget->metaObject()->className(),
                 stackCentralW->indexOf( stackCentralOldWidget ) );
    msg_Dbg( p_intf, "ShowTab request for %s", widget->metaObject()->className() );
641
#endif
642 643 644
    if ( stackCentralW->currentWidget() == widget )
        return;

François Cartegnie's avatar
François Cartegnie committed
645
    /* fixing when the playlist has been undocked after being hidden.
646 647 648 649
       restoreStackOldWidget() is called when video stops but
       stackCentralOldWidget would still be pointing to playlist */
    if ( widget == playlistWidget && !isPlDocked() )
        widget = bgWidget;
650

651
    stackCentralOldWidget = stackCentralW->currentWidget();
652 653
    stackWidgetsSizes[stackCentralOldWidget] = stackCentralW->size();

654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670
    /* If we are playing video, embedded */
    if( videoWidget && THEMIM->getIM()->hasVideo() )
    {
        /* Video -> Playlist */
        if( videoWidget == stackCentralOldWidget && widget == playlistWidget )
        {
            stackCentralW->removeWidget( videoWidget );
            videoWidget->show(); videoWidget->raise();
        }

        /* Playlist -> Video */
        if( playlistWidget == stackCentralOldWidget && widget == videoWidget )
        {
            playlistWidget->artContainer->removeWidget( videoWidget );
            videoWidget->show(); videoWidget->raise();
            stackCentralW->addWidget( videoWidget );
        }
671 672 673 674

        /* Embedded playlist -> Non-embedded playlist */
        if( bgWidget == stackCentralOldWidget && widget == videoWidget )
        {
675 676 677
            /* In rare case when video is started before the interface */
            if( playlistWidget != NULL )
                playlistWidget->artContainer->removeWidget( videoWidget );
678 679 680 681
            videoWidget->show(); videoWidget->raise();
            stackCentralW->addWidget( videoWidget );
            stackCentralW->setCurrentWidget( videoWidget );
        }
682 683
    }

684
    stackCentralW->setCurrentWidget( widget );
685 686
    if( b_autoresize )
        resizeStack( stackWidgetsSizes[widget].width(), stackWidgetsSizes[widget].height() );
687

688
#ifdef DEBUG_INTF
689 690 691 692 693 694
    msg_Dbg( p_intf, "Stack state changed to %s, index %i",
              stackCentralW->currentWidget()->metaObject()->className(),
              stackCentralW->currentIndex() );
    msg_Dbg( p_intf, "New stackCentralOldWidget %s at index %i",
              stackCentralOldWidget->metaObject()->className(),
              stackCentralW->indexOf( stackCentralOldWidget ) );
695
#endif
696 697 698 699 700 701 702 703

    /* This part is done later, to account for the new pl size */
    if( videoWidget && THEMIM->getIM()->hasVideo() &&
        videoWidget == stackCentralOldWidget && widget == playlistWidget )
    {
        playlistWidget->artContainer->addWidget( videoWidget );
        playlistWidget->artContainer->setCurrentWidget( videoWidget );
    }
704
}
705

706 707 708 709
void MainInterface::toggleFSC()
{
   if( !fullscreenControls ) return;

710
   IMEvent *eShow = new IMEvent( IMEvent::FullscreenControlToggle );
711 712 713
   QApplication::postEvent( fullscreenControls, eShow );
}

714 715 716
/****************************************************************************
 * Video Handling
 ****************************************************************************/
717

718
/**
719
 * NOTE:
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
720 721
 * You must not change the state of this object or other Qt UI objects,
 * from the video output thread - only from the Qt UI main loop thread.
722 723 724
 * All window provider queries must be handled through signals or events.
 * That's why we have all those emit statements...
 */
725 726 727
bool MainInterface::getVideo( struct vout_window_t *p_wnd,
                              unsigned int i_width, unsigned int i_height,
                              bool fullscreen )
728
{
729 730 731 732 733 734
    bool result;

    /* This is a blocking call signal. Results are stored directly in the
     * vout_window_t and boolean pointers. Beware of deadlocks! */
    emit askGetVideo( p_wnd, i_width, i_height, fullscreen, &result );
    return result;
735 736
}

737
void MainInterface::getVideoSlot( struct vout_window_t *p_wnd,
738
                                  unsigned i_width, unsigned i_height,
739
                                  bool fullscreen, bool *res )
740
{
741 742 743 744
    /* Hidden or minimized, activate */
    if( isHidden() || isMinimized() )
        toggleUpdateSystrayMenu();

745
    /* Request the videoWidget */
746 747 748 749 750
    if ( !videoWidget )
    {
        videoWidget = new VideoWidget( p_intf, stackCentralW );
        stackCentralW->addWidget( videoWidget );
    }
751 752
    *res = videoWidget->request( p_wnd );
    if( *res ) /* The videoWidget is available */
753
    {
754 755
        setVideoFullScreen( fullscreen );

756
        /* Consider the video active now */
757
        showVideo();
758 759

        /* Ask videoWidget to resize correctly, if we are in normal mode */
760 761 762 763 764 765 766 767
        if( b_autoresize ) {
#if HAS_QT56
            qreal factor = videoWidget->devicePixelRatioF();

            i_width = qRound( (qreal) i_width / factor );
            i_height = qRound( (qreal) i_height / factor );
#endif

768
            videoWidget->setSize( i_width, i_height );
769
        }
770
    }
771 772
}

773
/* Asynchronous call from the WindowClose function */
774
void MainInterface::releaseVideo( void )
775
{
776
    emit askReleaseVideo();
777 778
}

779
/* Function that is CONNECTED to the previous emit */
780
void MainInterface::releaseVideoSlot( void )
781
{
782 783
    /* This function is called when the embedded video window is destroyed,
     * or in the rare case that the embedded window is still here but the
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
784
     * Qt interface exits. */
785 786
    assert( videoWidget );
    videoWidget->release();
787
    setVideoOnTop( false );
788
    setVideoFullScreen( false );
789
    hideResumePanel();
Clément Stenac's avatar
Clément Stenac committed
790

791 792
    if( stackCentralW->currentWidget() == videoWidget )
        restoreStackOldWidget();
793 794
    else if( playlistWidget &&
             playlistWidget->artContainer->currentWidget() == videoWidget )
795 796 797 798
    {
        playlistWidget->artContainer->setCurrentIndex( 0 );
        stackCentralW->addWidget( videoWidget );
    }
Clément Stenac's avatar
Clément Stenac committed
799

800 801
    /* We don't want to have a blank video to popup */
    stackCentralOldWidget = bgWidget;
802 803
}

804
// The provided size is in physical pixels, coming from the core.
805 806
void MainInterface::setVideoSize( unsigned int w, unsigned int h )
{
807 808 809 810 811
    if (!isFullScreen() && !isMaximized() )
    {
        /* Resize video widget to video size, or keep it at the same
         * size. Call setSize() either way so that vout_window_ReportSize
         * will always get called.
812 813
         * If the video size is too large for the screen, resize it
         * to the screen size.
814 815
         */
        if (b_autoresize)
816 817
        {
            QRect screen = QApplication::desktop()->availableGeometry();
818 819 820 821 822 823
#if HAS_QT56
            float factor = videoWidget->devicePixelRatioF();
#else
            float factor = 1.0f;
#endif
            if( (float)h / factor > screen.height() )
824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841
            {
                w = screen.width();
                h = screen.height();
                if( !b_minimalView )
                {
                    if( menuBar()->isVisible() )
                        h -= menuBar()->height();
                    if( controls->isVisible() )
                        h -= controls->height();
                    if( statusBar()->isVisible() )
                        h -= statusBar()->height();
                    if( inputC->isVisible() )
                        h -= inputC->height();
                }
                h -= style()->pixelMetric(QStyle::PM_TitleBarHeight);
                h -= style()->pixelMetric(QStyle::PM_LayoutBottomMargin);
                h -= 2 * style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
            }
842 843 844 845 846 847 848
            else
            {
                // Convert the size in logical pixels
                w = qRound( (float)w / factor );
                h = qRound( (float)h / factor );
                msg_Dbg( p_intf, "Logical video size: %ux%u", w, h );
            }
849
            videoWidget->setSize( w, h );
850
        }
851 852 853
        else
            videoWidget->setSize( videoWidget->width(), videoWidget->height() );
    }
854 855
}

856 857 858 859 860 861
void MainInterface::videoSizeChanged( int w, int h )
{
    if( !playlistWidget || playlistWidget->artContainer->currentWidget() != videoWidget )
        resizeStack( w, h );
}

862 863 864
void MainInterface::setVideoFullScreen( bool fs )
{
    b_videoFullScreen = fs;
865 866 867 868 869 870 871
    if( fs )
    {
        int numscreen = var_InheritInteger( p_intf, "qt-fullscreen-screennumber" );
        /* if user hasn't defined screennumber, or screennumber that is bigger
         * than current number of screens, take screennumber where current interface
         * is
         */
872
        if( numscreen < 0 || numscreen >= QApplication::desktop()->screenCount() )
873 874
            numscreen = QApplication::desktop()->screenNumber( p_intf->p_sys->p_mi );

875 876
        if( fullscreenControls )
            fullscreenControls->setTargetScreen( numscreen );
877

878
        if ( numscreen >= 0 )
879
        {
880

881 882
            QRect screenres = QApplication::desktop()->screenGeometry( numscreen );
            lastWinScreen = windowHandle()->screen();
883 884 885 886
#ifdef QT5_HAS_WAYLAND
            if( !b_hasWayland )
                windowHandle()->setScreen(QGuiApplication::screens()[numscreen]);
#else
887
            windowHandle()->setScreen(QGuiApplication::screens()[numscreen]);
888
#endif
889 890 891 892 893 894 895 896 897 898 899 900 901 902 903

            /* To be sure window is on proper-screen in xinerama */
            if( !screenres.contains( pos() ) )
            {
                lastWinPosition = pos();
                lastWinSize = size();
                msg_Dbg( p_intf, "Moving video to correct position");
                move( QPoint( screenres.x(), screenres.y() ) );
            }

            /* */
            if( playlistWidget != NULL && playlistWidget->artContainer->currentWidget() == videoWidget )
            {
                showTab( videoWidget );
            }
904 905 906
        }

        /* */
907
        displayNormalView();
908 909 910 911
        setInterfaceFullScreen( true );
    }
    else
    {
912
        setMinimalView( b_minimalView );
913
        setInterfaceFullScreen( b_interfaceFullScreen );
914 915
#ifdef QT5_HAS_WAYLAND
        if( lastWinScreen != NULL && !b_hasWayland )
916
            windowHandle()->setScreen(lastWinScreen);
917 918 919 920
#else
        if( lastWinScreen != NULL )
            windowHandle()->setScreen(lastWinScreen);
#endif
921 922 923
        if( lastWinPosition.isNull() == false )
        {
            move( lastWinPosition );
924
            resizeWindow( lastWinSize.width(), lastWinSize.height() );
925 926 927 928
            lastWinPosition = QPoint();
            lastWinSize = QSize();
        }

929 930
    }
    videoWidget->sync();
931 932
}

933 934 935 936 937
void MainInterface::setHideMouse( bool hide )
{
    videoWidget->setCursor( hide ? Qt::BlankCursor : Qt::ArrowCursor );
}

938 939 940 941
/* Slot to change the video always-on-top flag.
 * Emit askVideoOnTop() to invoke this from other thread. */
void MainInterface::setVideoOnTop( bool on_top )
{
942 943 944 945
    //don't apply changes if user has already sets its interface on top
    if ( b_interfaceOnTop )
        return;

946 947
    Qt::WindowFlags oldflags = windowFlags(), newflags;

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
948
    if( on_top )
949 950 951
        newflags = oldflags | Qt::WindowStaysOnTopHint;
    else
        newflags = oldflags & ~Qt::WindowStaysOnTopHint;
952
    if( newflags != oldflags && !b_videoFullScreen )
953 954 955 956 957
    {
        setWindowFlags( newflags );
        show(); /* necessary to apply window flags */
    }
}
958

959 960 961 962 963 964 965 966 967 968
void MainInterface::setInterfaceAlwaysOnTop( bool on_top )
{
    b_interfaceOnTop = on_top;
    Qt::WindowFlags oldflags = windowFlags(), newflags;

    if( on_top )
        newflags = oldflags | Qt::WindowStaysOnTopHint;
    else
        newflags = oldflags & ~Qt::WindowStaysOnTopHint;
    if( newflags != oldflags && !b_videoFullScreen )
969 970 971 972 973 974
    {
        setWindowFlags( newflags );
        show(); /* necessary to apply window flags */
    }
}

975
/* Asynchronous call from WindowControl function */
976
int MainInterface::controlVideo( int i_query, va_list args )
977 978 979
{
    switch( i_query )
    {
980 981 982 983
    case VOUT_WINDOW_SET_SIZE:
    {
        unsigned int i_width  = va_arg( args, unsigned int );
        unsigned int i_height = va_arg( args, unsigned int );
984

985
        emit askVideoToResize( i_width, i_height );
986
        return VLC_SUCCESS;
987
    }
988
    case VOUT_WINDOW_SET_STATE:
989
    {
990 991
        unsigned i_arg = va_arg( args, unsigned );
        unsigned on_top = i_arg & VOUT_WINDOW_STATE_ABOVE;
992 993

        emit askVideoOnTop( on_top != 0 );
994 995
        return VLC_SUCCESS;
    }
996 997 998
    case VOUT_WINDOW_SET_FULLSCREEN:
    {
        bool b_fs = va_arg( args, int );
999

1000 1001 1002
        emit askVideoSetFullScreen( b_fs );
        return VLC_SUCCESS;
    }
1003 1004 1005 1006 1007 1008 1009
    case VOUT_WINDOW_HIDE_MOUSE:
    {
        bool b_hide = va_arg( args, int );

        emit askHideMouse( b_hide );
        return VLC_SUCCESS;
    }
1010 1011 1012
    default:
        msg_Warn( p_intf, "unsupported control query" );
        return VLC_EGENERIC;
1013 1014 1015
    }
}

1016 1017 1018 1019 1020 1021
/*****************************************************************************
 * Playlist, Visualisation and Menus handling
 *****************************************************************************/
/**
 * Toggle the playlist widget or dialog
 **/
1022
void MainInterface::createPlaylist()
1023
{
1024
    PlaylistDialog *dialog = PlaylistDialog::getInstance( p_intf );
1025

1026 1027
    if( b_plDocked )
    {
1028
        playlistWidget = dialog->exportPlaylistWidget();
1029
        stackCentralW->addWidget( playlistWidget );
1030
        stackWidgetsSizes[playlistWidget] = settings->value( "playlistSize", QSize( 600, 300 ) ).toSize();
1031
    }
1032
    CONNECT( dialog, visibilityChanged(bool), this, setPlaylistVisibility(bool) );
1033
}
1034

1035 1036
void MainInterface::togglePlaylist()
{
1037
    if( !playlistWidget ) createPlaylist();
1038

1039
    PlaylistDialog *dialog = PlaylistDialog::getInstance( p_intf );
1040
    if( b_plDocked )
1041
    {
1042 1043
        if ( dialog->hasPlaylistWidget() )
            playlistWidget = dialog->exportPlaylistWidget();
1044 1045 1046
        /* Playlist is not visible, show it */
        if( stackCentralW->currentWidget() != playlistWidget )
        {
1047 1048
            if( stackCentralW->indexOf( playlistWidget ) == -1 )
                stackCentralW->addWidget( playlistWidget );
1049 1050 1051 1052 1053 1054
            showTab( playlistWidget );
        }
        else /* Hide it! */
        {
            restoreStackOldWidget();
        }
1055
        playlistVisible = ( stackCentralW->currentWidget() == playlistWidget );
1056
    }
1057 1058 1059
    else
    {
        playlistVisible = !playlistVisible;
1060 1061 1062 1063 1064 1065
        if ( ! dialog->hasPlaylistWidget() )
            dialog->importPlaylistWidget( playlistWidget );
        if ( playlistVisible )
            dialog->show();
        else
            dialog->hide();
1066
    }
1067
    debug();
1068 1069
}

1070 1071 1072 1073 1074 1075 1076 1077
const Qt::Key MainInterface::kc[10] =
{
    Qt::Key_Up, Qt::Key_Up,
    Qt::Key_Down, Qt::Key_Down,
    Qt::Key_Left, Qt::Key_Right, Qt::Key_Left, Qt::Key_Right,
    Qt::Key_B, Qt::Key_A
};

1078
void MainInterface::dockPlaylist( bool p_docked )
1079
{
1080
    if( b_plDocked == p_docked ) return;
1081 1082 1083
    /* some extra check */
    if ( b_plDocked && !playlistWidget ) createPlaylist();

1084
    b_plDocked = p_docked;
1085
    PlaylistDialog *dialog = PlaylistDialog::getInstance( p_intf );
1086

1087
    if( !p_docked ) /* Previously docked */
1088
    {
1089
        playlistVisible = playlistWidget->isVisible();
1090 1091 1092 1093 1094 1095 1096

        /* repositioning the videowidget __before__ exporting the
           playlistwidget into the playlist dialog avoids two unneeded
           calls to the server in the qt library to reparent the underlying
           native window back and forth.
           For Wayland, this is mandatory since reparenting is not implemented.
           For X11 or Windows, this is just an optimization. */
1097
        if ( videoWidget && THEMIM->getIM()->hasVideo() )
1098
            showTab(videoWidget);
1099 1100
        else
            showTab(bgWidget);
1101 1102 1103 1104

        /* playlistwidget exported into the playlist dialog */
        stackCentralW->removeWidget( playlistWidget );
        dialog->importPlaylistWidget( playlistWidget );
1105
        if ( playlistVisible ) dialog->show();
1106
    }
1107
    else /* Previously undocked */
1108
    {
1109
        playlistVisible = dialog->isVisible() && !( videoWidget && THEMIM->getIM()->hasVideo() );
1110 1111
        dialog->hide();
        playlistWidget = dialog->exportPlaylistWidget();
1112
        stackCentralW->addWidget( playlistWidget );
1113 1114

        /* If playlist is invisible don't show it */
1115
        if( playlistVisible ) showTab( playlistWidget );
1116
    }
1117 1118
}

1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129