// (C) 2005 Max Howell (max.howell@methylblue.com) // See COPYING file for licensing information #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include //::open() #include //::timerEvent() #include #include #include #include #include #include #include #include //::stateChanged() #include //ctor #include //because XMLGUI is poorly designed #include #include #include "../debug.h" #include "../mxcl.library.h" #include "actions.h" #include "analyzer.h" #include "audioView.h" #include "codeineConfig.h" #include "extern.h" //dialog creation function definitions #include "fullScreenAction.h" #include "mainWindow.h" #include "playDialog.h" //::play() #include "playlistFile.h" #include "slider.h" #include "theStream.h" #include "volumeAction.h" #include "xineEngine.h" #ifndef NO_XTEST_EXTENSION extern "C" { #include #include } #endif constexpr auto kAspectSelectActionName = "aspect_ratio_select"; constexpr auto kAudioSelectActionName = "audio_channels_select"; constexpr auto kSubtitleSelectActionName = "subtitle_channels_select"; namespace Codeine { /// @see codeine.h TQWidget *mainWindow() { return kapp->mainWidget(); } MainWindow::MainWindow() : TDEMainWindow() , m_positionSlider( new Slider( this, 65535 ) ) , m_timeLabel( new TQLabel( " 0:00:00 ", this ) ) , m_titleLabel( new KSqueezedTextLabel( this ) ) { DEBUG_BLOCK clearWFlags( WDestructiveClose ); //we are allocated on the stack kapp->setMainWidget( this ); m_widgetStack = new TQWidgetStack(this, "m_widgetStack"); new VideoWindow( this ); m_audioView = new AudioView(this, "m_audioView"); // videoWindow() will be the initial widget. // m_audioView is raised when no video track is present. m_widgetStack->addWidget(videoWindow()); m_widgetStack->addWidget(m_audioView); setCentralWidget(m_widgetStack); setFocusProxy( videoWindow() ); // essential! See VideoWindow::event(), TQEvent::FocusOut // these have no affect beccause "KDE Knows Best" FFS setDockEnabled( toolBar(), TQt::DockRight, false ); //doesn't make sense due to our large horizontal slider setDockEnabled( toolBar(), TQt::DockLeft, false ); //as above m_titleLabel->setMargin( 2 ); m_timeLabel->setFont( TDEGlobalSettings::fixedFont() ); m_timeLabel->setAlignment( AlignCenter ); m_timeLabel->setMinimumSize( m_timeLabel->sizeHint() ); // work around a bug in KStatusBar // sizeHint width of statusbar seems to get stupidly large quickly statusBar()->setSizePolicy( TQSizePolicy::Ignored, TQSizePolicy::Maximum ); statusBar()->addWidget( m_titleLabel, 1, false ); statusBar()->addWidget( m_analyzer = new Analyzer::Block( this ), 0, true ); statusBar()->addWidget( m_timeLabel, 0, true ); setupActions(); setupGUI(); setStandardToolBarMenuEnabled( false ); //bah to setupGUI()! toolBar()->show(); //it's possible it would be hidden, but we don't want that as no UI way to show it! // only show dvd button when playing a dvd { struct KdeIsTehSuck : public TQObject { virtual bool eventFilter( TQObject*, TQEvent *e ) { if (e->type() != TQEvent::LayoutHint) return false; // basically, KDE shows all tool-buttons, even if they are // hidden after it does any layout operation. Yay for KDE. Yay. TQWidget *button = (TQWidget*)((TDEMainWindow*)mainWindow())->toolBar()->child( "toolbutton_toggle_dvd_menu" ); if (button) button->setShown( TheStream::url().protocol() == "dvd" ); return false; } } *o; o = new KdeIsTehSuck; toolBar()->installEventFilter( o ); insertChild( o ); } { /* Disable aspect/channel menus until the stream has loaded; * Make sure they have the same default item selected. */ TQStringList defaultItems("&Determine Automatically"); if (const auto aspectAction = dynamic_cast(action(kAspectSelectActionName))) { aspectAction->setToolTip(i18n("Aspect Ratio")); insertAspectRatioMenuItems(aspectAction); aspectAction->setEnabled(false); } if (const auto audioChannelAction = dynamic_cast(action(kAudioSelectActionName))) { audioChannelAction->setToolTip(i18n("Audio Channels")); audioChannelAction->setItems(defaultItems); audioChannelAction->setEnabled(false); } if (const auto subChannelAction = dynamic_cast(action(kSubtitleSelectActionName))) { subChannelAction->setToolTip(i18n("Subtitles")); subChannelAction->setItems(defaultItems); subChannelAction->setEnabled(false); } } TQObjectList *list = toolBar()->queryList( "TDEToolBarButton" ); if (list->isEmpty()) { MessageBox::error( i18n( "" PRETTY_NAME " could not load its interface, this probably means that " PRETTY_NAME " is not " "installed to the correct prefix. If you installed from packages please contact the packager, if " "you installed from source please try running the configure script again like this: " "
 % ./configure --prefix=`tde-config --prefix`
" ) ); std::exit( 1 ); } delete list; KXMLGUIClient::stateChanged( "empty" ); TDECmdLineArgs *args = TDECmdLineArgs::parsedArgs(); if( args->count() || args->isSet( "play-dvd" ) || kapp->isRestored() ) //we need to resize the window, so we can't show the window yet init(); else { //"faster" startup //TODO if we have a size stored for this video, do the "faster" route TQTimer::singleShot( 0, this, TQ_SLOT(init()) ); TQApplication::setOverrideCursor( KCursor::waitCursor() ); } } void MainWindow::init() { DEBUG_BLOCK connect( engine(), TQ_SIGNAL(statusMessage( const TQString& )), this, TQ_SLOT(engineMessage( const TQString& )) ); connect( engine(), TQ_SIGNAL(stateChanged( Engine::State )), this, TQ_SLOT(engineStateChanged( Engine::State )) ); connect( engine(), TQ_SIGNAL(titleChanged( const TQString& )), m_titleLabel, TQ_SLOT(setText( const TQString& )) ); connect( m_positionSlider, TQ_SIGNAL(valueChanged( int )), this, TQ_SLOT(showTime( int )) ); connect(engine(), TQ_SIGNAL(audioChannelsChanged(const TQStringList &)), this, TQ_SLOT(setAudioChannels(const TQStringList &))); connect(engine(), TQ_SIGNAL(subtitleChannelsChanged(const TQStringList &)), this, TQ_SLOT(setSubtitleChannels(const TQStringList &))); if( !engine()->init() ) { KMessageBox::error( this, i18n( "xine could not be successfully initialised. " PRETTY_NAME " will now exit. " "You can try to identify what is wrong with your xine installation using the xine-check command at a command-prompt.") ); std::exit( 2 ); } //would be dangerous for these to65535 happen before the videoWindow() is initialised setAcceptDrops( true ); connect( m_positionSlider, TQ_SIGNAL(sliderReleased( uint )), engine(), TQ_SLOT(seek( uint )) ); connect( statusBar(), TQ_SIGNAL(messageChanged( const TQString& )), engine(), TQ_SLOT(showOSD( const TQString& )) ); TQApplication::restoreOverrideCursor(); if( !kapp->isRestored() ) { TDECmdLineArgs &args = *TDECmdLineArgs::parsedArgs(); if (args.isSet( "play-dvd" )) open( "dvd:/" ); else if (args.count() > 0 ) { open( args.url( 0 ) ); args.clear(); adjustSize(); //will resize us to reflect the videoWindow's sizeHint() } else //show the welcome dialog playMedia( true ); // true = show in style of welcome dialog } else //session management must be done after the videoWindow() has been initialised restore( 1, false ); //don't do until videoWindow() is initialised! startTimer( 50 ); } MainWindow::~MainWindow() { DEBUG_FUNC_INFO hide(); //so we appear to have quit, and then sound fades out below delete videoWindow(); //fades out sound in dtor } bool MainWindow::queryExit() { if( toggleAction( "fullscreen" )->isChecked() ) { // there seems to be no other way to stop TDEMainWindow // saving the window state without any controls fullScreenToggled( false ); showNormal(); TQApplication::sendPostedEvents( this, 0 ); // otherwise TDEMainWindow saves the screensize as maximised Codeine::MessageBox::sorry( "This annoying messagebox is to get round a bug in either KDE or TQt. " "Just press OK and Codeine will quit." ); //NOTE not actually needed saveAutoSaveSettings(); hide(); } return true; } void MainWindow::setupActions() { DEBUG_BLOCK TDEActionCollection * const ac = actionCollection(); KStdAction::quit( kapp, TQ_SLOT(quit()), ac ); KStdAction::open( this, TQ_SLOT(playMedia()), ac, "play_media" )->setText( i18n("Play &Media...") ); connect( new FullScreenAction( this, ac ), TQ_SIGNAL(toggled( bool )), TQ_SLOT(fullScreenToggled( bool )) ); new PlayAction( this, TQ_SLOT(play()), ac ); new TDEAction( i18n("Stop"), "media-playback-stop", Key_S, engine(), TQ_SLOT(stop()), ac, "stop" ); new TDEToggleAction( i18n("Record"), "player_record", CTRL + Key_R, engine(), TQ_SLOT(record()), ac, "record" ); new TDEAction( i18n("Reset Video Scale"), "viewmag1", Key_Equal, videoWindow(), TQ_SLOT(resetZoom()), ac, "reset_zoom" ); new TDEAction( i18n("Media Information"), "messagebox_info", Key_I, this, TQ_SLOT(streamInformation()), ac, "information" ); new TDEAction( i18n("Menu Toggle"), "media-optical-dvd-unmounted", Key_R, engine(), TQ_SLOT(toggleDVDMenu()), ac, "toggle_dvd_menu" ); new TDEAction( i18n("&Capture Frame"), "frame_image", Key_C, this, TQ_SLOT(captureFrame()), ac, "capture_frame" ); new TDEAction( i18n("Video Settings..."), "configure", Key_V, this, TQ_SLOT(configure()), ac, "video_settings" ); new TDEAction( i18n("Configure xine..."), "configure", 0, this, TQ_SLOT(configure()), ac, "xine_settings" ); (new KWidgetAction( m_positionSlider, i18n("Position Slider"), nullptr, nullptr, nullptr, ac, "position_slider" ))->setAutoSized( true ); const auto audioSelectAction = new TDESelectAction(i18n("A&udio Channels"), 0, ac, kAudioSelectActionName); connect(audioSelectAction, TQ_SIGNAL(activated(int)), engine(), TQ_SLOT(setStreamParameter(int))); const auto subSelectAction = new TDESelectAction(i18n("&Subtitles"), 0, ac, kSubtitleSelectActionName); connect(subSelectAction, TQ_SIGNAL(activated(int)), engine(), TQ_SLOT(setStreamParameter(int))); const auto aspectSelectAction = new TDESelectAction(i18n("Aspect &Ratio"), 0, ac, kAspectSelectActionName); connect(aspectSelectAction, TQ_SIGNAL(activated(int)), engine(), TQ_SLOT(setStreamParameter(int))); m_volumeAction = new VolumeAction( toolBar(), ac ); } void MainWindow::saveProperties( TDEConfig *config ) { config->writeEntry( "url", TheStream::url().url() ); config->writeEntry( "time", engine()->time() ); } void MainWindow::readProperties( TDEConfig *config ) { if( engine()->load( config->readPathEntry( "url" ) ) ) engine()->play( config->readNumEntry( "time" ) ); } void MainWindow::timerEvent( TQTimerEvent* ) { static int counter = 0; if( engine()->state() == Engine::Playing ) { ++counter &= 1023; m_positionSlider->setValue( engine()->position() ); if( !m_positionSlider->isEnabled() && counter % 10 == 0 ) // 0.5 seconds // usually the slider emits a signal that updates the timeLabel // but not if the slider isn't moving because there is no length showTime(); #ifndef NO_XTEST_EXTENSION if( counter == 0 /*1020*/ ) { // 51 seconds //do at 0 to ensure screensaver doesn't happen before 51 seconds is up (somehow) const bool isOnThisDesktop = KWin::windowInfo( winId() ).isOnDesktop( KWin::currentDesktop() ); if( videoWindow()->isVisible() && isOnThisDesktop ) { int key = XKeysymToKeycode( x11Display(), XK_Shift_R ); XTestFakeKeyEvent( x11Display(), key, true, CurrentTime ); XTestFakeKeyEvent( x11Display(), key, false, CurrentTime ); XSync( x11Display(), false ); } } #endif } } void MainWindow::showTime( int pos ) { #define zeroPad( n ) n < 10 ? TQString("0%1").arg( n ) : TQString::number( n ) const int ms = (pos == -1) ? engine()->time() : int(engine()->length() * (pos / 65535.0)); const int s = ms / 1000; const int m = s / 60; const int h = m / 60; TQString time = zeroPad( s % 60 ); //seconds time.prepend( ':' ); time.prepend( zeroPad( m % 60 ) ); //minutes time.prepend( ':' ); time.prepend( TQString::number( h ) ); //hours m_timeLabel->setText( time ); } void MainWindow::engineMessage( const TQString &message ) { statusBar()->message( message, 3500 ); } bool MainWindow::open( const KURL &url ) { DEBUG_BLOCK debug() << url << endl; if( load( url ) ) { const int offset = TheStream::hasProfile() // adjust offset if we have session history for this video ? TheStream::profile()->readNumEntry( "Position", 0 ) : 0; return engine()->play( offset ); } return false; } bool MainWindow::load( const KURL &url ) { //FileWatch the file that is opened if( url.isEmpty() ) { MessageBox::sorry( i18n( "Codeine was asked to open an empty URL; it cannot." ) ); return false; } PlaylistFile playlist( url ); if( playlist.isPlaylist() ) { //TODO: problem is we return out of the function //statusBar()->message( i18n("Parsing playlist file...") ); if( playlist.isValid() ) return engine()->load( playlist.firstUrl() ); else { MessageBox::sorry( playlist.error() ); return false; } } if (url.protocol() == "media") { #define UDS_LOCAL_PATH (72 | TDEIO::UDS_STRING) TDEIO::UDSEntry e; if (!TDEIO::NetAccess::stat( url, e, nullptr )) MessageBox::sorry( "There was an internal error with the media slave..." ); else { TDEIO::UDSEntry::ConstIterator end = e.end(); for (TDEIO::UDSEntry::ConstIterator it = e.begin(); it != end; ++it) if ((*it).m_uds == UDS_LOCAL_PATH && !(*it).m_str.isEmpty()) return engine()->load( KURL::fromPathOrURL( (*it).m_str ) ); } } //let xine handle invalid, etc, KURLS //TODO it handles non-existant files with bad error message return engine()->load( url ); } void MainWindow::play() { switch( engine()->state() ) { case Engine::Loaded: engine()->play(); break; case Engine::Playing: case Engine::Paused: engine()->pause(); break; case Engine::Empty: default: playMedia(); break; } } void MainWindow::playMedia( bool show_welcome_dialog ) { PlayDialog dialog( this, show_welcome_dialog ); switch( dialog.exec() ) { case PlayDialog::FILE: { const TQString filter = engine()->fileFilter() + '|' + i18n("Supported Media Formats") + "\n*|" + i18n("All Files"); const KURL url = KFileDialog::getOpenURL( ":default", filter, this, i18n("Select A File To Play") ); open( url ); } break; case PlayDialog::RECENT_FILE: open( dialog.url() ); break; case PlayDialog::CDDA: open( "cdda:/1" ); break; case PlayDialog::VCD: open( "vcd://" ); // one / is not enough break; case PlayDialog::DVD: open( "dvd:/" ); break; } } class FullScreenToolBarHandler : TQObject { TDEToolBar *m_toolbar; int m_timer_id; bool m_stay_hidden_for_a_bit; TQPoint m_home; public: FullScreenToolBarHandler( TDEMainWindow *parent ) : TQObject( parent ) , m_toolbar( parent->toolBar() ) , m_timer_id( 0 ) , m_stay_hidden_for_a_bit( false ) { DEBUG_BLOCK parent->installEventFilter( this ); m_toolbar->installEventFilter( this ); } bool eventFilter( TQObject *o, TQEvent *e ) { if (o == parent() && e->type() == TQEvent::MouseMove) { killTimer( m_timer_id ); TQMouseEvent const * const me = (TQMouseEvent*)e; if (m_stay_hidden_for_a_bit) { // wait for a small pause before showing the toolbar again // usage = user removes mouse from toolbar after using it // toolbar disappears (usage is over) but usually we show // toolbar immediately when mouse is moved.. so we need this hack // HACK if user thrusts mouse to top, we assume they really want the toolbar // back. Is hack as 80% of users have at top, but 20% at bottom, we don't cater // for the 20% as lots more code, for now. if (me->pos().y() < m_toolbar->height()) goto show_toolbar; m_timer_id = startTimer( 100 ); } else { if (m_toolbar->isHidden()) { if (m_home.isNull()) m_home = me->pos(); else if ((m_home - me->pos()).manhattanLength() > 6) // then cursor has moved far enough to trigger show toolbar show_toolbar: m_toolbar->show(), m_home = TQPoint(); else // cursor hasn't moved far enough yet // don't reset timer below, return instead return false; } // reset the hide timer m_timer_id = startTimer( VideoWindow::CURSOR_HIDE_TIMEOUT ); } } if (o == parent() && e->type() == TQEvent::Resize) { //we aren't managed by mainWindow when at FullScreen videoWindow()->move( 0, 0 ); videoWindow()->resize( ((TQWidget*)o)->size() ); videoWindow()->lower(); } if (o == m_toolbar) switch (e->type()) { case TQEvent::Enter: m_stay_hidden_for_a_bit = false; killTimer( m_timer_id ); break; case TQEvent::Leave: m_toolbar->hide(); m_stay_hidden_for_a_bit = true; killTimer( m_timer_id ); m_timer_id = startTimer( 100 ); break; default: break; } return false; } void timerEvent( TQTimerEvent* ) { if (m_stay_hidden_for_a_bit) ; else if (!m_toolbar->hasMouse()) m_toolbar->hide(); m_stay_hidden_for_a_bit = false; } }; void MainWindow::fullScreenToggled( bool isFullScreen ) { static FullScreenToolBarHandler *s_handler; DEBUG_FUNC_INFO if( isFullScreen ) toolBar()->setPalette( palette() ), // due to 2px spacing in TQMainWindow :( setPaletteBackgroundColor( TQt::black ); // due to 2px spacing else toolBar()->unsetPalette(), unsetPalette(); toolBar()->setMovingEnabled( !isFullScreen ); toolBar()->setHidden( isFullScreen && engine()->state() == Engine::Playing ); reinterpret_cast(menuBar())->setHidden( isFullScreen ); statusBar()->setHidden( isFullScreen ); setMouseTracking( isFullScreen ); /// @see mouseMoveEvent() if (isFullScreen) s_handler = new FullScreenToolBarHandler( this ); else delete s_handler; // prevent videoWindow() moving around when mouse moves setCentralWidget( isFullScreen ? nullptr : videoWindow() ); } void MainWindow::configure() { const TQCString sender = this->sender()->name(); if( sender == "video_settings" ) Codeine::showVideoSettingsDialog( this ); else if( sender == "xine_settings" ) Codeine::showXineConfigurationDialog( this, *engine() ); } void MainWindow::streamInformation() { MessageBox::information( TheStream::information(), i18n("Media Information") ); } void MainWindow::setAudioChannels(const TQStringList &channels) const { DEBUG_FUNC_INFO /* Xine uses -1 and -2 to indicate that a channel should be determined automatically or * turned off. TDESelectAction inserts items starting from index 0, so we add 2 to the * channel returned from TheStream to match. */ if (const auto audioSelection = dynamic_cast(action(kAudioSelectActionName))) { TQStringList audioChannels(channels); audioChannels.prepend("&Determine Automatically"); audioChannels.prepend("&Off"); audioSelection->setItems(audioChannels); audioSelection->popupMenu()->insertSeparator(2); audioSelection->setCurrentItem(TheStream::audioChannel() + 2); audioSelection->setEnabled(channels.count()); } else { Debug::error() << "Failed to update the audio channels (selection menu not found)" << endl; } } void MainWindow::setSubtitleChannels(const TQStringList &channels) const { DEBUG_FUNC_INFO if (const auto subSelection = dynamic_cast(action(kSubtitleSelectActionName))) { TQStringList subChannels(channels); subChannels.prepend("&Determine Automatically"); subChannels.prepend("&Off"); subSelection->setItems(subChannels); subSelection->popupMenu()->insertSeparator(2); subSelection->setCurrentItem(TheStream::subtitleChannel() + 2); subSelection->setEnabled(channels.count()); } else { Debug::error() << "Failed to update the subtitle channels (selection menu not found)" << endl; } } void MainWindow::dragEnterEvent( TQDragEnterEvent *e ) { e->accept( KURLDrag::canDecode( e ) ); } void MainWindow::dropEvent( TQDropEvent *e ) { KURL::List list; KURLDrag::decode( e, list ); if( !list.isEmpty() ) open( list.first() ); else engineMessage( i18n("Sorry, no media was found in the drop") ); } void MainWindow::keyPressEvent( TQKeyEvent *e ) { #define seek( step ) { \ const int new_pos = m_positionSlider->value() step; \ engine()->seek( new_pos > 0 ? (uint)new_pos : 0 ); \ } switch( e->key() ) { case TQt::Key_Left: seek( -500 ); break; case TQt::Key_Right: seek( +500 ); break; case Key_Escape: KWin::clearState( winId(), NET::FullScreen ); default: ; } #undef seek } TQPopupMenu* MainWindow::menu( const TQString& name ) { // KXMLGUI is "really good". return dynamic_cast(factory()->container( name, this )); } /// Convenience class for other classes that need access to the actionCollection TDEActionCollection* actionCollection() { return static_cast(kapp->mainWidget())->actionCollection(); } /// Convenience class for other classes that need access to the actions TDEAction* action( const char *name ) { #define QT_FATAL_ASSERT MainWindow *mainWindow = nullptr; TDEActionCollection *actionCollection = nullptr; TDEAction *action = nullptr; mainWindow = dynamic_cast(kapp->mainWidget()); if (mainWindow) { actionCollection = mainWindow->actionCollection(); if (actionCollection) { action = actionCollection->action(name); } } Q_ASSERT(mainWindow); Q_ASSERT(actionCollection); Q_ASSERT(action); return action; } } //namespace Codeine #include "mainWindow.moc"