summaryrefslogtreecommitdiffstats
path: root/src/app
diff options
context:
space:
mode:
authorMichele Calgaro <michele.calgaro@yahoo.it>2020-06-13 22:45:28 +0900
committerMichele Calgaro <michele.calgaro@yahoo.it>2020-06-13 22:45:28 +0900
commit5f44f7b187093ef290315b7f8766b540a31de35f (patch)
tree27ffb7b218199ca04f240c390c52426c65f45dce /src/app
downloadcodeine-5f44f7b187093ef290315b7f8766b540a31de35f.tar.gz
codeine-5f44f7b187093ef290315b7f8766b540a31de35f.zip
Initial code import from debian snapshot
https://snapshot.debian.org/package/codeine/1.0.1-3.dfsg-3.1/ Signed-off-by: Michele Calgaro <michele.calgaro@yahoo.it>
Diffstat (limited to 'src/app')
-rw-r--r--src/app/SConscript59
-rw-r--r--src/app/actions.cpp27
-rw-r--r--src/app/actions.h26
-rw-r--r--src/app/adjustSizeButton.cpp125
-rw-r--r--src/app/adjustSizeButton.h37
-rw-r--r--src/app/analyzer.cpp131
-rw-r--r--src/app/analyzer.h75
-rw-r--r--src/app/captureFrame.cpp296
-rw-r--r--src/app/config.h20
-rw-r--r--src/app/extern.h28
-rw-r--r--src/app/fht.cpp262
-rw-r--r--src/app/fht.h126
-rw-r--r--src/app/fullScreenAction.cpp96
-rw-r--r--src/app/fullScreenAction.h27
-rw-r--r--src/app/insertAspectRatioMenuItems.cpp24
-rw-r--r--src/app/listView.cpp39
-rw-r--r--src/app/main.cpp52
-rw-r--r--src/app/mainWindow.cpp714
-rw-r--r--src/app/mainWindow.h75
-rw-r--r--src/app/playDialog.cpp114
-rw-r--r--src/app/playDialog.h36
-rw-r--r--src/app/playlistFile.cpp123
-rw-r--r--src/app/playlistFile.h36
-rw-r--r--src/app/slider.cpp145
-rw-r--r--src/app/slider.h52
-rw-r--r--src/app/stateChange.cpp195
-rw-r--r--src/app/theStream.cpp144
-rw-r--r--src/app/theStream.h50
-rw-r--r--src/app/videoSettings.cpp135
-rw-r--r--src/app/videoSettings.h26
-rw-r--r--src/app/videoWindow.cpp380
-rw-r--r--src/app/volumeAction.cpp114
-rw-r--r--src/app/volumeAction.h29
-rw-r--r--src/app/xineConfig.cpp321
-rw-r--r--src/app/xineConfig.h69
-rw-r--r--src/app/xineEngine.cpp876
-rw-r--r--src/app/xineEngine.h159
-rw-r--r--src/app/xineScope.c148
-rw-r--r--src/app/xineScope.h38
39 files changed, 5429 insertions, 0 deletions
diff --git a/src/app/SConscript b/src/app/SConscript
new file mode 100644
index 0000000..3c35c32
--- /dev/null
+++ b/src/app/SConscript
@@ -0,0 +1,59 @@
+
+############################
+## load the config
+
+## Use the environment and the tools set in the top-level
+## SConstruct file (set with 'Export') - this is very important
+
+Import( '*' )
+myenv=env.Copy()
+
+#############################
+## the programs to build
+
+# we put the stuff that could fail due to bad xine.h locations, etc. at the beginning
+# so if the build fails the user knows quickly
+app_sources = Split("""
+ xineEngine.cpp
+ xineConfig.cpp
+ xineScope.c
+ theStream.cpp
+ videoWindow.cpp
+ videoSettings.cpp
+ captureFrame.cpp
+
+ actions.cpp
+ stateChange.cpp
+ slider.cpp
+ analyzer.cpp
+ playDialog.cpp
+ listView.cpp
+ adjustSizeButton.cpp
+ fullScreenAction.cpp
+ insertAspectRatioMenuItems.cpp
+ playlistFile.cpp
+ volumeAction.cpp
+
+ ../mxcl.library.cpp
+
+ main.cpp
+ mainWindow.cpp""")
+
+KDEprogram( "codeine", app_sources, myenv )
+
+
+############################
+## Customization
+
+## Additional include paths for compiling the source files
+## Always add '../' (top-level directory) because moc makes code that needs it
+KDEaddpaths( ['./', '../', '../../'], myenv )
+
+## Necessary libraries to link against
+KDEaddlibs( ['qt-mt', 'kio', 'kdecore', 'kdeui', 'xine', 'Xtst'], myenv )
+
+## This shows how to add other link flags to the program
+myenv['LINKFLAGS'].append('-L/usr/X11R6/lib')
+
+## If you are using QThread, add this line
+# myenv.AppendUnique( CPPFLAGS = ['-DQT_THREAD_SUPPORT'] )
diff --git a/src/app/actions.cpp b/src/app/actions.cpp
new file mode 100644
index 0000000..a767a2c
--- /dev/null
+++ b/src/app/actions.cpp
@@ -0,0 +1,27 @@
+// Copyright 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#include "actions.h"
+#include "debug.h"
+#include "mxcl.library.h"
+#include <qtoolbutton.h>
+#include "xineEngine.h"
+
+namespace Codeine
+{
+ PlayAction::PlayAction( QObject *receiver, const char *slot, KActionCollection *ac )
+ : KToggleAction( i18n("Play"), "player_play", Qt::Key_Space, receiver, slot, ac, "play" )
+ {}
+
+ void
+ PlayAction::setChecked( bool b )
+ {
+ if( videoWindow()->state() == Engine::Empty && sender() && QCString(sender()->className()) == "KToolBarButton" ) {
+ // clicking play when empty means open PlayMediaDialog, but we have to uncheck the toolbar button
+ // as KDElibs sets that checked automatically..
+ ((QToolButton*)sender())->setOn( false );
+ }
+ else
+ KToggleAction::setChecked( b );
+ }
+}
diff --git a/src/app/actions.h b/src/app/actions.h
new file mode 100644
index 0000000..4f2f589
--- /dev/null
+++ b/src/app/actions.h
@@ -0,0 +1,26 @@
+// (c) 2004 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#ifndef CODEINEACTIONS_H
+#define CODEINEACTIONS_H
+
+#include <kactionclasses.h> //baseclass
+#include <kactioncollection.h> //convenience
+
+namespace Codeine
+{
+ KActionCollection *actionCollection(); ///defined in mainWindow.cpp
+ KAction *action( const char* ); ///defined in mainWindow.cpp
+ inline KToggleAction *toggleAction( const char *name ) { return (KToggleAction*)action( name ); }
+
+ class PlayAction : public KToggleAction
+ {
+ public:
+ PlayAction( QObject *receiver, const char *slot, KActionCollection* );
+
+ protected:
+ virtual void setChecked( bool );
+ };
+}
+
+#endif
diff --git a/src/app/adjustSizeButton.cpp b/src/app/adjustSizeButton.cpp
new file mode 100644
index 0000000..041b01c
--- /dev/null
+++ b/src/app/adjustSizeButton.cpp
@@ -0,0 +1,125 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#include "adjustSizeButton.h"
+#include "extern.h"
+#include <kpushbutton.h>
+#include <qapplication.h>
+#include <qevent.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qpainter.h>
+#include "theStream.h"
+#include "xineEngine.h" //videoWindow()
+
+
+QString i18n( const char *text );
+
+namespace Codeine
+{
+ AdjustSizeButton::AdjustSizeButton( QWidget *parent )
+ : QFrame( parent )
+ , m_counter( 0 )
+ , m_stage( 1 )
+ , m_offset( 0 )
+ {
+ parent->installEventFilter( this );
+
+ setPalette( QApplication::palette() ); //videoWindow has different palette
+ setFrameStyle( QFrame::Plain | QFrame::Box );
+
+ m_preferred = new KPushButton( KGuiItem( i18n("Preferred Scale"), "viewmag" ), this );
+ connect( m_preferred, SIGNAL(clicked()), qApp->mainWidget(), SLOT(adjustSize()) );
+ connect( m_preferred, SIGNAL(clicked()), SLOT(deleteLater()) );
+
+ m_oneToOne = new KPushButton( KGuiItem( i18n("Scale 100%"), "viewmag1" ), this );
+ connect( m_oneToOne, SIGNAL(clicked()), (QObject*)videoWindow(), SLOT(resetZoom()) );
+ connect( m_oneToOne, SIGNAL(clicked()), SLOT(deleteLater()) );
+
+ QBoxLayout *hbox = new QHBoxLayout( this, 8, 6 );
+ QBoxLayout *vbox = new QVBoxLayout( hbox );
+ vbox->addWidget( new QLabel( i18n( "<b>Adjust video scale?" ), this ) );
+ vbox->addWidget( m_preferred );
+ vbox->addWidget( m_oneToOne );
+ hbox->addWidget( m_thingy = new QFrame( this ) );
+
+ m_thingy->setFixedWidth( fontMetrics().width( "X" ) );
+ m_thingy->setFrameStyle( QFrame::Plain | QFrame::Box );
+ m_thingy->setPaletteForegroundColor( paletteBackgroundColor().dark() );
+
+ QEvent e( QEvent::Resize );
+ eventFilter( 0, &e );
+
+ adjustSize();
+ show();
+
+ m_timerId = startTimer( 5 );
+ }
+
+ void
+ AdjustSizeButton::timerEvent( QTimerEvent* )
+ {
+ QFrame *&h = m_thingy;
+
+ switch( m_stage )
+ {
+ case 1: //raise
+ move();
+ m_offset++;
+
+ if( m_offset > height() )
+ killTimer( m_timerId ),
+ m_timerId = startTimer( 40 ),
+ m_stage = 2;
+
+ break;
+
+ case 2: //fill in pause timer bar
+ if( m_counter < h->height() - 3 )
+ QPainter( h ).fillRect( 2, 2, h->width() - 4, m_counter, palette().active().highlight() );
+
+ if( !hasMouse() )
+ m_counter++;
+
+ if( m_counter > h->height() + 5 ) //pause for 360ms before lowering
+ m_stage = 3,
+ killTimer( m_timerId ),
+ m_timerId = startTimer( 6 );
+
+ break;
+
+ case 3: //lower
+ if( hasMouse() ) {
+ m_stage = 1;
+ m_counter = 0;
+ m_thingy->repaint();
+ break; }
+
+ m_offset--;
+ move();
+
+ if( m_offset < 0 )
+ deleteLater();
+ }
+ }
+
+ bool
+ AdjustSizeButton::eventFilter( QObject *o, QEvent *e )
+ {
+ if( e->type() == QEvent::Resize ) {
+ const QSize preferredSize = TheStream::profile()->readSizeEntry( "Preferred Size" );
+ const QSize defaultSize = TheStream::defaultVideoSize();
+ const QSize parentSize = parentWidget()->size();
+
+ m_preferred->setEnabled( preferredSize.isValid() && parentSize != preferredSize && defaultSize != preferredSize );
+ m_oneToOne->setEnabled( defaultSize != parentSize );
+
+ move();
+
+ if( !m_preferred->isEnabled() && !m_oneToOne->isEnabled() && m_counter == 0 )
+ deleteLater();
+ }
+
+ return false;
+ }
+}
diff --git a/src/app/adjustSizeButton.h b/src/app/adjustSizeButton.h
new file mode 100644
index 0000000..6eed27c
--- /dev/null
+++ b/src/app/adjustSizeButton.h
@@ -0,0 +1,37 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#ifndef CODEINE_ADJUST_SIZE_BUTTON_H
+#define CODEINE_ADJUST_SIZE_BUTTON_H
+
+#include <qframe.h>
+
+namespace Codeine
+{
+ class AdjustSizeButton : public QFrame
+ {
+ int m_counter;
+ int m_stage;
+ int m_offset;
+ int m_timerId;
+
+ QWidget *m_preferred;
+ QWidget *m_oneToOne;
+
+ QFrame *m_thingy;
+
+ public:
+ AdjustSizeButton( QWidget *parent );
+
+ private:
+ virtual void timerEvent( QTimerEvent* );
+ virtual bool eventFilter( QObject*, QEvent* );
+
+ inline void move()
+ {
+ QWidget::move( parentWidget()->width() - width(), parentWidget()->height() - m_offset );
+ }
+ };
+}
+
+#endif
diff --git a/src/app/analyzer.cpp b/src/app/analyzer.cpp
new file mode 100644
index 0000000..c9b8637
--- /dev/null
+++ b/src/app/analyzer.cpp
@@ -0,0 +1,131 @@
+// (c) 2004 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#include "analyzer.h"
+#include "codeine.h"
+#include "debug.h"
+#include <math.h> //interpolate()
+#include <qevent.h> //event()
+#include "xineEngine.h"
+
+#include "fht.cpp"
+
+template<class W>
+Analyzer::Base<W>::Base( QWidget *parent, uint timeout )
+ : W( parent, "Analyzer" )
+ , m_timeout( timeout )
+{}
+
+template<class W> bool
+Analyzer::Base<W>::event( QEvent *e )
+{
+ switch( e->type() ) {
+ case QEvent::Hide:
+ m_timer.stop();
+ break;
+
+ case QEvent::Show:
+ m_timer.start( timeout() );
+ break;
+
+ default:
+ ;
+ }
+
+ return QWidget::event( e );
+}
+
+
+Analyzer::Base2D::Base2D( QWidget *parent, uint timeout )
+ : Base<QWidget>( parent, timeout )
+{
+ setWFlags( Qt::WNoAutoErase ); //no flicker
+ connect( &m_timer, SIGNAL(timeout()), SLOT(draw()) );
+}
+
+void
+Analyzer::Base2D::draw()
+{
+ switch( Codeine::engine()->state() ) {
+ case Engine::Playing:
+ {
+ const Engine::Scope &thescope = Codeine::engine()->scope();
+ static Analyzer::Scope scope( Analyzer::SCOPE_SIZE );
+
+ for( int x = 0; x < Analyzer::SCOPE_SIZE; ++x )
+ scope[x] = double(thescope[x]) / (1<<15);
+
+ transform( scope );
+ analyze( scope );
+
+ scope.resize( Analyzer::SCOPE_SIZE );
+
+ bitBlt( this, 0, 0, canvas() );
+ break;
+ }
+ case Engine::Paused:
+ break;
+
+ default:
+ erase();
+ }
+}
+
+void
+Analyzer::Base2D::resizeEvent( QResizeEvent* )
+{
+ m_canvas.resize( size() );
+ m_canvas.fill( colorGroup().background() );
+}
+
+
+
+// Author: Max Howell <max.howell@methylblue.com>, (C) 2003
+// Copyright: See COPYING file that comes with this distribution
+
+#include <qpainter.h>
+
+Analyzer::Block::Block( QWidget *parent )
+ : Analyzer::Base2D( parent, 20 )
+{
+ setMinimumWidth( 64 ); //-1 is padding, no drawing takes place there
+ setMaximumWidth( 128 );
+
+ //TODO yes, do height for width
+}
+
+void
+Analyzer::Block::transform( Analyzer::Scope &scope ) //pure virtual
+{
+ static FHT fht( Analyzer::SCOPE_SIZE_EXP );
+
+ for( uint x = 0; x < scope.size(); ++x )
+ scope[x] *= 2;
+
+ float *front = static_cast<float*>( &scope.front() );
+
+ fht.spectrum( front );
+ fht.scale( front, 1.0 / 40 );
+}
+
+#include <math.h>
+void
+Analyzer::Block::analyze( const Analyzer::Scope &s )
+{
+ canvas()->fill( colorGroup().foreground().light() );
+
+ QPainter p( canvas() );
+ p.setPen( colorGroup().background() );
+
+ const double F = double(height()) / (log10( 256 ) * 1.1 /*<- max. amplitude*/);
+
+ for( uint x = 0; x < s.size(); ++x )
+ //we draw the blank bit
+ p.drawLine( x, 0, x, int(height() - log10( s[x] * 256.0 ) * F) );
+}
+
+int
+Analyzer::Block::heightForWidth( int w ) const
+{
+ return w / 2;
+}
diff --git a/src/app/analyzer.h b/src/app/analyzer.h
new file mode 100644
index 0000000..6fdb12f
--- /dev/null
+++ b/src/app/analyzer.h
@@ -0,0 +1,75 @@
+// (c) 2004 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#ifndef ANALYZER_H
+#define ANALYZER_H
+
+#ifdef __FreeBSD__
+ #include <sys/types.h>
+#endif
+
+#include <qpixmap.h> //stack allocated and convenience
+#include <qtimer.h> //stack allocated
+#include <qwidget.h> //baseclass
+#include <vector> //included for convenience
+
+namespace Analyzer
+{
+ typedef std::vector<float> Scope;
+
+ template<class W> class Base : public W
+ {
+ public:
+ uint timeout() const { return m_timeout; }
+
+ protected:
+ Base( QWidget*, uint );
+
+ virtual void transform( Scope& ) = 0;
+ virtual void analyze( const Scope& ) = 0;
+
+ private:
+ virtual bool event( QEvent* );
+
+ protected:
+ QTimer m_timer;
+ uint m_timeout;
+ };
+
+ class Base2D : public Base<QWidget>
+ {
+ Q_OBJECT
+ public:
+ const QPixmap *canvas() const { return &m_canvas; }
+
+ private slots:
+ void draw();
+
+ protected:
+ Base2D( QWidget*, uint timeout );
+
+ QPixmap *canvas() { return &m_canvas; }
+
+ void paintEvent( QPaintEvent* ) { if( !m_canvas.isNull() ) bitBlt( this, 0, 0, canvas() ); }
+ void resizeEvent( QResizeEvent* );
+
+ private:
+ QPixmap m_canvas;
+ };
+
+ class Block : public Analyzer::Base2D
+ {
+ public:
+ Block( QWidget* );
+
+ protected:
+ virtual void transform( Analyzer::Scope& );
+ virtual void analyze( const Analyzer::Scope& );
+
+ virtual int heightForWidth( int ) const;
+
+ virtual void show() {} //TODO temporary as the scope plugin causes freezes
+ };
+}
+
+#endif
diff --git a/src/app/captureFrame.cpp b/src/app/captureFrame.cpp
new file mode 100644
index 0000000..4cba5fd
--- /dev/null
+++ b/src/app/captureFrame.cpp
@@ -0,0 +1,296 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#include "debug.h"
+#include <kfiledialog.h>
+#include <kpreviewwidgetbase.h>
+#include <kpushbutton.h>
+#include <kstatusbar.h>
+#include <kstdguiitem.h>
+#include "mainWindow.h"
+#include "mxcl.library.h"
+#include <qdialog.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qimage.h>
+#include <qlayout.h>
+#include <qpainter.h>
+#include <qstringlist.h>
+#include "theStream.h"
+#include "xineEngine.h"
+#include <xine.h>
+
+
+namespace Codeine {
+
+class FrameCapturePreview : public KPreviewWidgetBase
+{
+ QImage m_frame;
+
+ virtual void showPreview( const KURL& ) {}
+ virtual void clearPreview() {}
+
+ virtual void paintEvent( QPaintEvent* )
+ {
+ QPainter painter( this );
+
+ const uint h = int( double(m_frame.height()) / m_frame.width() * (width()-5) );
+ const uint y = (height() - h) / 2;
+ painter.drawImage( QRect( 5, y, width(), h ), m_frame );
+
+ const QString text = QString("%1x%2").arg( m_frame.width() ).arg( m_frame.height() );
+ const uint x = (width() - fontMetrics().width( text ))/2;
+ painter.drawText( x, y + h + fontMetrics().height() + 5, text );
+ }
+
+public:
+ FrameCapturePreview( const QImage& frame, QWidget *parent )
+ : KPreviewWidgetBase( parent )
+ , m_frame( frame )
+ {
+ setMinimumWidth( 200 );
+ }
+};
+
+
+class FrameCaptureDialog : public QDialog
+{
+ const QImage m_frame;
+ const QString m_time;
+ const QString m_title;
+
+ void message( const QString &text ) { ((MainWindow*)parentWidget())->statusBar()->message( text, 4000 ); }
+
+public:
+ FrameCaptureDialog( const QImage &frame, const QString &time, MainWindow *parent )
+ : QDialog( parent, 0, false /*modal*/, Qt::WDestructiveClose )
+ , m_frame( frame )
+ , m_time( time )
+ , m_title( TheStream::prettyTitle() )
+ {
+ (new QVBoxLayout( this ))->setAutoAdd( true );
+ (new QLabel( this ))->setPixmap( frame );
+
+ QHBox *box = new QHBox( this );
+ KPushButton *o = new KPushButton( KStdGuiItem::save(), box );
+ connect( o, SIGNAL(clicked()), SLOT(accept()) );
+
+ o = new KPushButton( KStdGuiItem::cancel(), box );
+ o->setText( i18n("Discard") );
+ connect( o, SIGNAL(clicked()), SLOT(reject()) );
+
+ setCaption( i18n("Capture - %1").arg( time ) );
+ setFixedSize( sizeHint() );
+
+ show();
+
+ //TODO don't activate
+ //TODO move to the parent's side - not centrally aligned
+ }
+
+ ~FrameCaptureDialog()
+ {
+ delete [] m_frame.bits();
+ }
+
+ virtual void accept()
+ {
+ KFileDialog dialog( ":frame_capture", i18n("*.png|PNG Format\n*.jpeg|JPEG Format"), this, 0, false );
+ dialog.setOperationMode( KFileDialog::Saving );
+ dialog.setCaption( i18n("Save Frame") );
+ dialog.setSelection( m_title + " - " + m_time + ".png" );
+ dialog.setPreviewWidget( new FrameCapturePreview( m_frame, &dialog ) );
+
+ if( dialog.exec() == Accepted ) {
+ const QString fileName = dialog.selectedFile();
+ if( fileName.isEmpty() )
+ return;
+
+ const QString type = dialog.currentFilter().remove( 0, 2 ).upper();
+ if( m_frame.save( fileName, type ) )
+ message( i18n("%1 saved successfully").arg( fileName ) );
+ else
+ message( i18n("Sorry, could not save %1").arg( fileName ) );
+ }
+
+ deleteLater();
+ }
+};
+
+
+void
+MainWindow::captureFrame()
+{
+ new FrameCaptureDialog( videoWindow()->captureFrame(), m_timeLabel->text(), this );
+}
+
+
+/************************************************************
+ * Helpers to convert yuy and yv12 frames to rgb *
+ * code from gxine modified for 32bit output *
+ * Copyright (C) 2000-2003 the xine project *
+ ************************************************************/
+
+static void
+yuy2Toyv12( uint8_t *y, uint8_t *u, uint8_t *v, uint8_t *input, int w, int h )
+{
+ const int w2 = w / 2;
+ for( int j, i = 0; i < h; i += 2 ) {
+ for( j = 0; j < w2; j++ )
+ {
+ // packed YUV 422 is: Y[i] U[i] Y[i+1] V[i]
+ *(y++) = *(input++);
+ *(u++) = *(input++);
+ *(y++) = *(input++);
+ *(v++) = *(input++);
+ }
+
+ // down sampling
+ for( j = 0; j < w2; j++ ) {
+ // skip every second line for U and V
+ *(y++) = *(input++);
+ input++;
+ *(y++) = *(input++);
+ input++;
+ }
+ }
+}
+
+static uchar*
+yv12ToRgb( uint8_t *src_y, uint8_t *src_u, uint8_t *src_v, const int w, const int h )
+{
+ /// Create rgb data from yv12
+
+ #define clip_8_bit(val) \
+ { \
+ if( val < 0 ) \
+ val = 0; \
+ else if( val > 255 ) \
+ val = 255; \
+ }
+
+ int y, u, v;
+ int r, g, b;
+
+ int sub_i_uv;
+ int sub_j_uv;
+
+ const int uv_width = w / 2;
+ const int uv_height = h / 2;
+
+ uchar * const rgb = new uchar[(w * h * 4)]; //qt needs a 32bit align
+ if( !rgb )
+ return 0;
+
+ for( int i = 0; i < h; ++i ) {
+ // calculate u & v rows
+ sub_i_uv = ((i * uv_height) / h);
+
+ for( int j = 0; j < w; ++j ) {
+ // calculate u & v columns
+ sub_j_uv = (j * uv_width) / w;
+
+ /***************************************************
+ *
+ * Colour conversion from
+ * http://www.inforamp.net/~poynton/notes/colour_and_gamma/ColorFAQ.html#RTFToC30
+ *
+ * Thanks to Billy Biggs <vektor@dumbterm.net>
+ * for the pointer and the following conversion.
+ *
+ * R' = [ 1.1644 0 1.5960 ] ([ Y' ] [ 16 ])
+ * G' = [ 1.1644 -0.3918 -0.8130 ] * ([ Cb ] - [ 128 ])
+ * B' = [ 1.1644 2.0172 0 ] ([ Cr ] [ 128 ])
+ *
+ * Where in xine the above values are represented as
+ *
+ * Y' == image->y
+ * Cb == image->u
+ * Cr == image->v
+ *
+ ***************************************************/
+
+ y = src_y[(i * w) + j] - 16;
+ u = src_u[(sub_i_uv * uv_width) + sub_j_uv] - 128;
+ v = src_v[(sub_i_uv * uv_width) + sub_j_uv] - 128;
+
+ r = (int)((1.1644 * (double)y) + (1.5960 * (double)v));
+ g = (int)((1.1644 * (double)y) - (0.3918 * (double)u) - (0.8130 * (double)v));
+ b = (int)((1.1644 * (double)y) + (2.0172 * (double)u));
+
+ clip_8_bit( r );
+ clip_8_bit( g );
+ clip_8_bit( b );
+
+ rgb[(i * w + j) * 4 + 0] = b;
+ rgb[(i * w + j) * 4 + 1] = g;
+ rgb[(i * w + j) * 4 + 2] = r;
+ rgb[(i * w + j) * 4 + 3] = 0;
+ }
+ }
+
+ return rgb;
+}
+
+/************************************************************/
+
+
+QImage
+VideoWindow::captureFrame() const
+{
+ DEBUG_BLOCK
+
+ int ratio, format, w, h;
+ if( !xine_get_current_frame( *engine(), &w, &h, &ratio, &format, NULL ) )
+ return QImage();
+
+ uint8_t *yuv = new uint8_t[((w+8) * (h+1) * 2)];
+ if( yuv == 0 ) {
+ Debug::error() << "Not enough memory to make screenframe!\n";
+ return QImage(); }
+
+ xine_get_current_frame( *engine(), &w, &h, &ratio, &format, yuv );
+
+ // convert to yv12 if necessary
+ uint8_t *y = 0, *u = 0, *v = 0;
+ switch( format )
+ {
+ case XINE_IMGFMT_YUY2: {
+ uint8_t *yuy2 = yuv;
+
+ yuv = new uint8_t[(w * h * 2)];
+ if( yuv == 0 ) {
+ Debug::error() << "Not enough memory to make screenframe!\n";
+ delete [] yuy2;
+ return QImage(); }
+
+ y = yuv;
+ u = yuv + w * h;
+ v = yuv + w * h * 5 / 4;
+
+ yuy2Toyv12( y, u, v, yuy2, w, h );
+
+ delete [] yuy2;
+ } break;
+
+ case XINE_IMGFMT_YV12:
+ y = yuv;
+ u = yuv + w * h;
+ v = yuv + w * h * 5 / 4;
+ break;
+
+ default:
+ Debug::warning() << "Format " << format << " not supported!\n";
+ delete [] yuv;
+ return QImage();
+ }
+
+ // convert to rgb
+ uchar *rgb = yv12ToRgb( y, u, v, w, h );
+ QImage frame( rgb, w, h, 32, 0, 0, QImage::IgnoreEndian );
+ delete [] yuv;
+
+ return frame;
+}
+
+}
diff --git a/src/app/config.h b/src/app/config.h
new file mode 100644
index 0000000..fbab5e8
--- /dev/null
+++ b/src/app/config.h
@@ -0,0 +1,20 @@
+// (c) 2004 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#ifndef CODEINECONFIG_H
+#define CODEINECONFIG_H
+
+#include <kconfig.h>
+#include <kglobal.h>
+
+namespace Codeine
+{
+ static inline KConfig *config( const QString &group )
+ {
+ KConfig* const instance = KGlobal::config();
+ instance->setGroup( group );
+ return instance;
+ }
+}
+
+#endif
diff --git a/src/app/extern.h b/src/app/extern.h
new file mode 100644
index 0000000..20e49fd
--- /dev/null
+++ b/src/app/extern.h
@@ -0,0 +1,28 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#ifndef CODEINE_EXTERN_H
+#define CODEINE_EXTERN_H
+
+extern "C"
+{
+ typedef struct xine_s xine_t;
+}
+
+class QPopupMenu;
+class QWidget;
+
+namespace Codeine
+{
+ class VideoWindow;
+ class XineEngine;
+
+ VideoWindow* const engine(); //defined in xineEngine.h
+ VideoWindow* const videoWindow(); //defined in xineEngine.h
+
+ void showVideoSettingsDialog( QWidget* );
+ void showXineConfigurationDialog( QWidget*, xine_t* );
+ void insertAspectRatioMenuItems( QPopupMenu* );
+}
+
+#endif
diff --git a/src/app/fht.cpp b/src/app/fht.cpp
new file mode 100644
index 0000000..4d03851
--- /dev/null
+++ b/src/app/fht.cpp
@@ -0,0 +1,262 @@
+// FHT - Fast Hartley Transform Class
+//
+// Copyright (C) 2004 Melchior FRANZ - mfranz@kde.org
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
+//
+// $Id: fht.cpp,v 1.3 2004/06/05 20:20:36 mfranz Exp $
+
+#include <math.h>
+#include <string.h>
+#include "fht.h"
+
+
+FHT::FHT(int n) :
+ m_buf(0),
+ m_tab(0),
+ m_log(0)
+{
+ if (n < 3) {
+ m_num = 0;
+ m_exp2 = -1;
+ return;
+ }
+ m_exp2 = n;
+ m_num = 1 << n;
+ if (n > 3) {
+ m_buf = new float[m_num];
+ m_tab = new float[m_num * 2];
+ makeCasTable();
+ }
+}
+
+
+FHT::~FHT()
+{
+ delete[] m_buf;
+ delete[] m_tab;
+ delete[] m_log;
+}
+
+
+void FHT::makeCasTable(void)
+{
+ float d, *costab, *sintab;
+ int ul, ndiv2 = m_num / 2;
+
+ for (costab = m_tab, sintab = m_tab + m_num / 2 + 1, ul = 0; ul < m_num; ul++) {
+ d = M_PI * ul / ndiv2;
+ *costab = *sintab = cos(d);
+
+ costab += 2, sintab += 2;
+ if (sintab > m_tab + m_num * 2)
+ sintab = m_tab + 1;
+ }
+}
+
+
+float* FHT::copy(float *d, float *s)
+{
+ return (float *)memcpy(d, s, m_num * sizeof(float));
+}
+
+
+float* FHT::clear(float *d)
+{
+ return (float *)memset(d, 0, m_num * sizeof(float));
+}
+
+
+void FHT::scale(float *p, float d)
+{
+ for (int i = 0; i < (m_num / 2); i++)
+ *p++ *= d;
+}
+
+
+void FHT::ewma(float *d, float *s, float w)
+{
+ for (int i = 0; i < (m_num / 2); i++, d++, s++)
+ *d = *d * w + *s * (1 - w);
+}
+
+
+static inline float sind(float d) { return sin(d * M_PI / 180); }
+void FHT::pattern(float *p, bool rect = false)
+{
+ static float f = 1.0;
+ static float h = 0.1;
+ int i;
+ for (i = 0; i < 3 * m_num / 4; i++, p++) {
+ float o = 360.0 * i / m_num;
+ *p = sind(f * o);
+ if (rect)
+ *p = *p < 0 ? -1.0 : 1.0;
+ }
+ for (; i < m_num; i++)
+ *p++ = 0.0;
+ if (f > m_num / 2.0 || f < .05)
+ h = -h;
+ f += h;
+}
+
+
+void FHT::logSpectrum(float *out, float *p)
+{
+ int n = m_num / 2, i, j, k, *r;
+ if (!m_log) {
+ m_log = new int[n];
+ float f = n / log10(n);
+ for (i = 0, r = m_log; i < n; i++, r++) {
+ j = int(rint(log10(i + 1.0) * f));
+ *r = j >= n ? n - 1 : j;
+ }
+ }
+ semiLogSpectrum(p);
+ *out++ = *p = *p / 100;
+ for (k = i = 1, r = m_log; i < n; i++) {
+ j = *r++;
+ if (i == j)
+ *out++ = p[i];
+ else {
+ float base = p[k - 1];
+ float step = (p[j] - base) / (j - (k - 1));
+ for (float corr = 0; k <= j; k++, corr += step)
+ *out++ = base + corr;
+ }
+ }
+}
+
+
+void FHT::semiLogSpectrum(float *p)
+{
+ float e;
+ power2(p);
+ for (int i = 0; i < (m_num / 2); i++, p++) {
+ e = 10.0 * log10(sqrt(*p * .5));
+ *p = e < 0 ? 0 : e;
+ }
+}
+
+
+void FHT::spectrum(float *p)
+{
+ power2(p);
+ for (int i = 0; i < (m_num / 2); i++, p++)
+ *p = (float)sqrt(*p * .5);
+}
+
+
+void FHT::power(float *p)
+{
+ power2(p);
+ for (int i = 0; i < (m_num / 2); i++)
+ *p++ *= .5;
+}
+
+
+void FHT::power2(float *p)
+{
+ int i;
+ float *q;
+ _transform(p, m_num, 0);
+
+ *p = (*p * *p), *p += *p, p++;
+
+ for (i = 1, q = p + m_num - 2; i < (m_num / 2); i++, --q)
+ *p++ = (*p * *p) + (*q * *q);
+}
+
+
+void FHT::transform(float *p)
+{
+ if (m_num == 8)
+ transform8(p);
+ else
+ _transform(p, m_num, 0);
+}
+
+
+void FHT::transform8(float *p)
+{
+ float a, b, c, d, e, f, g, h, b_f2, d_h2;
+ float a_c_eg, a_ce_g, ac_e_g, aceg, b_df_h, bdfh;
+
+ a = *p++, b = *p++, c = *p++, d = *p++;
+ e = *p++, f = *p++, g = *p++, h = *p;
+ b_f2 = (b - f) * M_SQRT2;
+ d_h2 = (d - h) * M_SQRT2;
+
+ a_c_eg = a - c - e + g;
+ a_ce_g = a - c + e - g;
+ ac_e_g = a + c - e - g;
+ aceg = a + c + e + g;
+
+ b_df_h = b - d + f - h;
+ bdfh = b + d + f + h;
+
+ *p = a_c_eg - d_h2;
+ *--p = a_ce_g - b_df_h;
+ *--p = ac_e_g - b_f2;
+ *--p = aceg - bdfh;
+ *--p = a_c_eg + d_h2;
+ *--p = a_ce_g + b_df_h;
+ *--p = ac_e_g + b_f2;
+ *--p = aceg + bdfh;
+}
+
+
+void FHT::_transform(float *p, int n, int k)
+{
+ if (n == 8) {
+ transform8(p + k);
+ return;
+ }
+
+ int i, j, ndiv2 = n / 2;
+ float a, *t1, *t2, *t3, *t4, *ptab, *pp;
+
+ for (i = 0, t1 = m_buf, t2 = m_buf + ndiv2, pp = &p[k]; i < ndiv2; i++)
+ *t1++ = *pp++, *t2++ = *pp++;
+
+ memcpy(p + k, m_buf, sizeof(float) * n);
+
+ _transform(p, ndiv2, k);
+ _transform(p, ndiv2, k + ndiv2);
+
+ j = m_num / ndiv2 - 1;
+ t1 = m_buf;
+ t2 = t1 + ndiv2;
+ t3 = p + k + ndiv2;
+ ptab = m_tab;
+ pp = p + k;
+
+ a = *ptab++ * *t3++;
+ a += *ptab * *pp;
+ ptab += j;
+
+ *t1++ = *pp + a;
+ *t2++ = *pp++ - a;
+
+ for (i = 1, t4 = p + k + n; i < ndiv2; i++, ptab += j) {
+ a = *ptab++ * *t3++;
+ a += *ptab * *--t4;
+
+ *t1++ = *pp + a;
+ *t2++ = *pp++ - a;
+ }
+ memcpy(p + k, m_buf, sizeof(float) * n);
+}
+
diff --git a/src/app/fht.h b/src/app/fht.h
new file mode 100644
index 0000000..3dc5387
--- /dev/null
+++ b/src/app/fht.h
@@ -0,0 +1,126 @@
+// FHT - Fast Hartley Transform Class
+//
+// Copyright (C) 2004 Melchior FRANZ - mfranz@kde.org
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
+//
+// $Id: fht.h,v 1.3 2004/06/05 20:20:36 mfranz Exp $
+
+#ifndef FHT_H
+#define FHT_H
+
+/**
+ * Implementation of the Hartley Transform after Bracewell's discrete
+ * algorithm. The algorithm is subject to US patent No. 4,646,256 (1987)
+ * but was put into public domain by the Board of Trustees of Stanford
+ * University in 1994 and is now freely available[1].
+ *
+ * [1] Computer in Physics, Vol. 9, No. 4, Jul/Aug 1995 pp 373-379
+ */
+class FHT
+{
+ int m_exp2;
+ int m_num;
+ float *m_buf;
+ float *m_tab;
+ int *m_log;
+
+ /**
+ * Create a table of CAS (cosine and sine) values.
+ * Has only to be done in the constructor and saves from
+ * calculating the same values over and over while transforming.
+ */
+ void makeCasTable();
+
+ /**
+ * Recursive in-place Hartley transform. For internal use only!
+ */
+ void _transform(float *, int, int);
+
+ public:
+ /**
+ * Prepare transform for data sets with @f$2^n@f$ numbers, whereby @f$n@f$
+ * should be at least 3. Values of more than 3 need a trigonometry table.
+ * @see makeCasTable()
+ */
+ FHT(int);
+
+ ~FHT();
+ inline int sizeExp() const { return m_exp2; }
+ inline int size() const { return m_num; }
+ float *copy(float *, float *);
+ float *clear(float *);
+ void scale(float *, float);
+
+ /**
+ * Exponentially Weighted Moving Average (EWMA) filter.
+ * @param d is the filtered data.
+ * @param s is fresh input.
+ * @param w is the weighting factor.
+ */
+ void ewma(float *d, float *s, float w);
+
+ /**
+ * Test routine to create wobbling sine or rectangle wave.
+ * @param d destination vector.
+ * @param rect rectangle if true, sine otherwise.
+ */
+ void pattern(float *d, bool rect);
+
+ /**
+ * Logarithmic audio spectrum. Maps semi-logarithmic spectrum
+ * to logarithmic frequency scale, interpolates missing values.
+ * A logarithmic index map is calculated at the first run only.
+ * @param p is the input array.
+ * @param out is the spectrum.
+ */
+ void logSpectrum(float *out, float *p);
+
+ /**
+ * Semi-logarithmic audio spectrum.
+ */
+ void semiLogSpectrum(float *);
+
+ /**
+ * Fourier spectrum.
+ */
+ void spectrum(float *);
+
+ /**
+ * Calculates a mathematically correct FFT power spectrum.
+ * If further scaling is applied later, use power2 instead
+ * and factor the 0.5 in the final scaling factor.
+ * @see FHT::power2()
+ */
+ void power(float *);
+
+ /**
+ * Calculates an FFT power spectrum with doubled values as a
+ * result. The values need to be multiplied by 0.5 to be exact.
+ * Note that you only get @f$2^{n-1}@f$ power values for a data set
+ * of @f$2^n@f$ input values.
+ * @see FHT::power()
+ */
+ void power2(float *);
+
+ /**
+ * Discrete Hartley transform of data sets with 8 values.
+ */
+ void transform8(float *);
+
+ void transform(float *);
+};
+
+#endif
diff --git a/src/app/fullScreenAction.cpp b/src/app/fullScreenAction.cpp
new file mode 100644
index 0000000..f28da84
--- /dev/null
+++ b/src/app/fullScreenAction.cpp
@@ -0,0 +1,96 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#include "extern.h"
+#include "fullScreenAction.h"
+#include <klocale.h>
+#include <kwin.h>
+#include <qwidget.h>
+#include "xineEngine.h" //videoWindow()
+
+
+FullScreenAction::FullScreenAction( QWidget* window, KActionCollection *parent )
+ : KToggleAction( QString::null, Key_F, 0, 0, parent, "fullscreen" )
+ , m_window( window )
+ , m_shouldBeDisabled( false )
+ , m_state( 0 )
+{
+ window->installEventFilter( this );
+ setChecked( false );
+}
+
+void
+FullScreenAction::setChecked( bool setChecked )
+{
+ KToggleAction::setChecked( setChecked );
+
+ m_window->raise();
+
+ const int id = m_window->winId();
+ if( setChecked ) {
+ setText( i18n("Exit F&ull Screen Mode") );
+ setIcon("window_nofullscreen");
+ m_state = KWin::windowInfo( id ).state();
+ KWin::setState( id, NET::FullScreen );
+ }
+ else {
+ setText(i18n("F&ull Screen Mode"));
+ setIcon("window_fullscreen");
+ KWin::clearState( id, NET::FullScreen );
+ KWin::setState( id, m_state ); // get round bug in KWin where it forgets maximisation state
+ }
+
+ if( setChecked == false && m_shouldBeDisabled )
+ setEnabled( false );
+}
+
+void
+FullScreenAction::setEnabled( bool setEnabled )
+{
+ if( setEnabled == false && isChecked() )
+ // don't disable the action if we are currently in fullscreen mode
+ // as then the user can't exit fullscreen mode! Instead disable it
+ // when we next get toggled out of fullscreen mode
+ m_shouldBeDisabled = true;
+
+ else {
+ //FIXME Codeine specific (because videoWindow isn't the window we control, we control the KMainWindow)
+ //NOTE also if the videoWindow is hidden at some point, this is broken..
+ //TODO new type of actionclass that event filters and is always correct state
+ if( setEnabled && reinterpret_cast<QWidget*>(Codeine::videoWindow())->isHidden() )
+ setEnabled = false;
+
+ m_shouldBeDisabled = false;
+ KToggleAction::setEnabled( setEnabled );
+ }
+}
+
+bool
+FullScreenAction::eventFilter( QObject *o, QEvent *e )
+{
+ if( o == m_window )
+ switch( e->type() ) {
+ #if QT_VERSION >= 0x030300
+ case QEvent::WindowStateChange:
+ #else
+ case QEvent::ShowFullScreen:
+ case QEvent::ShowNormal:
+ case QEvent::ShowMaximized:
+ case QEvent::ShowMinimized:
+ #endif
+ if (m_window->isFullScreen() != isChecked())
+ slotActivated(); // setChecked( window->isFullScreen()) wouldn't emit signals
+
+ if (m_window->isFullScreen() && !isEnabled()) {
+ m_shouldBeDisabled = true;
+ setEnabled( true );
+ }
+
+ break;
+
+ default:
+ ;
+ }
+
+ return false;
+}
diff --git a/src/app/fullScreenAction.h b/src/app/fullScreenAction.h
new file mode 100644
index 0000000..4234633
--- /dev/null
+++ b/src/app/fullScreenAction.h
@@ -0,0 +1,27 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#include <kaction.h>
+
+
+/**
+ * @class FullSCreenAction
+ * @author Max Howell <max.howell@methylblue.com>
+ * @short Adapted KToggleFullScreenAction, mainly because that class is shit
+ */
+class FullScreenAction : public KToggleAction
+{
+public:
+ FullScreenAction( QWidget *window, KActionCollection* );
+
+ virtual void setChecked( bool );
+ virtual void setEnabled( bool );
+
+protected:
+ virtual bool eventFilter( QObject* o, QEvent* e );
+
+private:
+ QWidget *m_window;
+ bool m_shouldBeDisabled;
+ unsigned long m_state;
+};
diff --git a/src/app/insertAspectRatioMenuItems.cpp b/src/app/insertAspectRatioMenuItems.cpp
new file mode 100644
index 0000000..353fe43
--- /dev/null
+++ b/src/app/insertAspectRatioMenuItems.cpp
@@ -0,0 +1,24 @@
+// Copyright 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#include <qpopupmenu.h>
+#include <xine.h>
+
+QString i18n( const char *text );
+
+
+namespace Codeine
+{
+ void
+ insertAspectRatioMenuItems( QPopupMenu *menu )
+ {
+ menu->insertItem( i18n( "Determine &Automatically" ), XINE_VO_ASPECT_AUTO );
+ menu->insertSeparator();
+ menu->insertItem( i18n( "&Square (1:1)" ), XINE_VO_ASPECT_SQUARE );
+ menu->insertItem( i18n( "&4:3" ), XINE_VO_ASPECT_4_3 );
+ menu->insertItem( i18n( "Ana&morphic (16:9)" ), XINE_VO_ASPECT_ANAMORPHIC );
+ menu->insertItem( i18n( "&DVB (2.11:1)" ), XINE_VO_ASPECT_DVB );
+
+ menu->setItemChecked( XINE_VO_ASPECT_AUTO, true );
+ }
+}
diff --git a/src/app/listView.cpp b/src/app/listView.cpp
new file mode 100644
index 0000000..b7990ec
--- /dev/null
+++ b/src/app/listView.cpp
@@ -0,0 +1,39 @@
+// (c) 2004 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#ifndef CODEINELISTVIEW_CPP
+#define CODEINELISTVIEW_CPP
+
+#include <klistview.h>
+
+namespace Codeine
+{
+ class ListView : public KListView
+ {
+ public:
+ ListView( QWidget *parent ) : KListView( parent )
+ {
+ addColumn( QString::null, 0 );
+ addColumn( QString::null );
+
+ setResizeMode( LastColumn );
+ setMargin( 2 );
+ setSorting( -1 );
+ setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
+ setAllColumnsShowFocus( true );
+ setItemMargin( 3 );
+ }
+
+ virtual QSize sizeHint() const
+ {
+ const QSize sh = KListView::sizeHint();
+
+ return QSize( sh.width(),
+ childCount() == 0
+ ? 50
+ : QMIN( sh.height(), childCount() * (firstChild()->height()) + margin() * 2 + 4 + reinterpret_cast<QWidget*>(header())->height() ) );
+ }
+ };
+}
+
+#endif
diff --git a/src/app/main.cpp b/src/app/main.cpp
new file mode 100644
index 0000000..7c0f6fc
--- /dev/null
+++ b/src/app/main.cpp
@@ -0,0 +1,52 @@
+// (c) 2004 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#include "codeine.h"
+#include <kaboutdata.h>
+#include <kapplication.h>
+#include <kcmdlineargs.h>
+#include "mainWindow.h"
+#include <X11/Xlib.h>
+
+
+static KAboutData aboutData( APP_NAME,
+ I18N_NOOP(PRETTY_NAME), APP_VERSION,
+ I18N_NOOP("A video player that has a usability focus"), KAboutData::License_GPL_V2,
+ I18N_NOOP("Copyright 2006, Max Howell"), 0,
+ "http://www.methylblue.com/codeine/",
+ "codeine@methylblue.com" );
+
+static const KCmdLineOptions options[] = {
+ { "+[URL]", I18N_NOOP( "Play 'URL'" ), 0 },
+ { "play-dvd", I18N_NOOP( "Play DVD Video" ), 0 },
+ { 0, 0, 0 } };
+
+int
+main( int argc, char **argv )
+{
+ //we need to do this, says adrianS from SuSE
+ if( !XInitThreads() )
+ return 1;
+
+ aboutData.addCredit( "Mike Diehl", I18N_NOOP("Handbook") );
+ aboutData.addCredit( "The Kaffeine Developers", I18N_NOOP("Great reference code") );
+ aboutData.addCredit( "Eric Prydz", I18N_NOOP("The video for \"Call on Me\" encouraged plenty of debugging! ;)") );
+ aboutData.addCredit( "David Vignoni", I18N_NOOP("The current Codeine icon") );
+ aboutData.addCredit( "Ian Monroe", I18N_NOOP("Patches, advice and moral support") );
+
+
+ KCmdLineArgs::init( argc, argv, &aboutData );
+ KCmdLineArgs::addCmdLineOptions( options );
+
+ KApplication application;
+ int returnValue;
+
+ {
+ Codeine::MainWindow mainWindow;
+ mainWindow.show();
+
+ returnValue = application.exec();
+ }
+
+ return returnValue;
+}
diff --git a/src/app/mainWindow.cpp b/src/app/mainWindow.cpp
new file mode 100644
index 0000000..856e0b6
--- /dev/null
+++ b/src/app/mainWindow.cpp
@@ -0,0 +1,714 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#include "actions.h"
+#include "analyzer.h"
+#include "config.h"
+#include "configure.h"
+#include <cstdlib>
+#include "debug.h"
+#include "extern.h" //dialog creation function definitions
+#include "fullScreenAction.h"
+#include <kapplication.h>
+#include <kcmdlineargs.h>
+#include <kcursor.h>
+#include <kfiledialog.h> //::open()
+#include <kglobalsettings.h> //::timerEvent()
+#include <kio/netaccess.h>
+#include <ksqueezedtextlabel.h>
+#include <kstatusbar.h>
+#include <ktoolbar.h>
+#include <kurldrag.h>
+#include <kwin.h>
+#include "mainWindow.h"
+#include "playDialog.h" //::play()
+#include "playlistFile.h"
+#include "mxcl.library.h"
+#include <qcstring.h>
+#include <qdesktopwidget.h>
+#include <qevent.h> //::stateChanged()
+#include <qlayout.h> //ctor
+#include <qpopupmenu.h> //because XMLGUI is poorly designed
+#include <qobjectlist.h>
+#include "slider.h"
+#include "theStream.h"
+#include "volumeAction.h"
+#include "xineEngine.h"
+
+#ifndef NO_XTEST_EXTENSION
+extern "C"
+{
+ #include <X11/extensions/XTest.h>
+ #include <X11/keysym.h>
+}
+#endif
+
+
+namespace Codeine {
+
+
+ /// @see codeine.h
+ QWidget *mainWindow() { return kapp->mainWidget(); }
+
+
+MainWindow::MainWindow()
+ : KMainWindow()
+ , m_positionSlider( new Slider( this, 65535 ) )
+ , m_timeLabel( new QLabel( " 0:00:00 ", this ) )
+ , m_titleLabel( new KSqueezedTextLabel( this ) )
+{
+ DEBUG_BLOCK
+
+ clearWFlags( WDestructiveClose ); //we are allocated on the stack
+
+ kapp->setMainWidget( this );
+
+ new VideoWindow( this );
+ setCentralWidget( videoWindow() );
+ setFocusProxy( videoWindow() ); // essential! See VideoWindow::event(), QEvent::FocusOut
+
+ // these have no affect beccause "KDE Knows Best" FFS
+ setDockEnabled( toolBar(), Qt::DockRight, false ); //doesn't make sense due to our large horizontal slider
+ setDockEnabled( toolBar(), Qt::DockLeft, false ); //as above
+
+ m_titleLabel->setMargin( 2 );
+ m_timeLabel->setFont( KGlobalSettings::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( QSizePolicy::Ignored, QSizePolicy::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 QObject
+ {
+ virtual bool eventFilter( QObject*, QEvent *e )
+ {
+ if (e->type() != QEvent::LayoutHint)
+ return false;
+
+ // basically, KDE shows all tool-buttons, even if they are
+ // hidden after it does any layout operation. Yay for KDE. Yay.
+ QWidget *button = (QWidget*)((KMainWindow*)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 );
+ }
+
+ {
+ QPopupMenu *menu = 0, *settings = static_cast<QPopupMenu*>(factory()->container( "settings", this ));
+ int id = SubtitleChannelsMenuItemId, index = 0;
+
+ #define make_menu( name, text ) \
+ menu = new QPopupMenu( this, name ); \
+ menu->setCheckable( true ); \
+ connect( menu, SIGNAL(activated( int )), engine(), SLOT(setStreamParameter( int )) ); \
+ connect( menu, SIGNAL(aboutToShow()), SLOT(aboutToShowMenu()) ); \
+ settings->insertItem( text, menu, id, index ); \
+ settings->setItemEnabled( id, false ); \
+ id++, index++;
+
+ make_menu( "subtitle_channels_menu", i18n( "&Subtitles" ) );
+ make_menu( "audio_channels_menu", i18n( "A&udio Channels" ) );
+ make_menu( "aspect_ratio_menu", i18n( "Aspect &Ratio" ) );
+ #undef make_menu
+
+ Codeine::insertAspectRatioMenuItems( menu ); //so we don't have to include xine.h here
+
+ settings->insertSeparator( index );
+ }
+
+ QObjectList *list = toolBar()->queryList( "KToolBarButton" );
+ if (list->isEmpty()) {
+ MessageBox::error( i18n(
+ "<qt>" 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 <b>configure</b> script again like this: "
+ "<pre> % ./configure --prefix=`kde-config --prefix`</pre>" ) );
+
+ std::exit( 1 );
+ }
+ delete list;
+
+ KXMLGUIClient::stateChanged( "empty" );
+
+ KCmdLineArgs *args = KCmdLineArgs::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
+ QTimer::singleShot( 0, this, SLOT(init()) );
+ QApplication::setOverrideCursor( KCursor::waitCursor() ); }
+}
+
+void
+MainWindow::init()
+{
+ DEBUG_BLOCK
+
+ connect( engine(), SIGNAL(statusMessage( const QString& )), this, SLOT(engineMessage( const QString& )) );
+ connect( engine(), SIGNAL(stateChanged( Engine::State )), this, SLOT(engineStateChanged( Engine::State )) );
+ connect( engine(), SIGNAL(channelsChanged( const QStringList& )), this, SLOT(setChannels( const QStringList& )) );
+ connect( engine(), SIGNAL(titleChanged( const QString& )), m_titleLabel, SLOT(setText( const QString& )) );
+ connect( m_positionSlider, SIGNAL(valueChanged( int )), this, SLOT(showTime( int )) );
+
+ if( !engine()->init() ) {
+ KMessageBox::error( this, i18n(
+ "<qt>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 <b>xine-check</b> 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, SIGNAL(sliderReleased( uint )), engine(), SLOT(seek( uint )) );
+ connect( statusBar(), SIGNAL(messageChanged( const QString& )), engine(), SLOT(showOSD( const QString& )) );
+
+ QApplication::restoreOverrideCursor();
+
+ if( !kapp->isRestored() ) {
+ KCmdLineArgs &args = *KCmdLineArgs::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 KMainWindow
+ // saving the window state without any controls
+ fullScreenToggled( false );
+ showNormal();
+ QApplication::sendPostedEvents( this, 0 );
+ // otherwise KMainWindow saves the screensize as maximised
+ Codeine::MessageBox::sorry(
+ "This annoying messagebox is to get round a bug in either KDE or Qt. "
+ "Just press OK and Codeine will quit." );
+ //NOTE not actually needed
+ saveAutoSaveSettings();
+ hide();
+ }
+
+ return true;
+}
+
+void
+MainWindow::setupActions()
+{
+ DEBUG_BLOCK
+
+ KActionCollection * const ac = actionCollection();
+
+ KStdAction::quit( kapp, SLOT(quit()), ac );
+ KStdAction::open( this, SLOT(playMedia()), ac, "play_media" )->setText( i18n("Play &Media...") );
+ connect( new FullScreenAction( this, ac ), SIGNAL(toggled( bool )), SLOT(fullScreenToggled( bool )) );
+
+ new PlayAction( this, SLOT(play()), ac );
+ new KAction( i18n("Stop"), "player_stop", Key_S, engine(), SLOT(stop()), ac, "stop" );
+
+ new KToggleAction( i18n("Record"), "player_record", CTRL + Key_R, engine(), SLOT(record()), ac, "record" );
+
+ new KAction( i18n("Reset Video Scale"), "viewmag1", Key_Equal, videoWindow(), SLOT(resetZoom()), ac, "reset_zoom" );
+ new KAction( i18n("Media Information"), "messagebox_info", Key_I, this, SLOT(streamInformation()), ac, "information" );
+ new KAction( i18n("Menu Toggle"), "dvd_unmount", Key_R, engine(), SLOT(toggleDVDMenu()), ac, "toggle_dvd_menu" );
+ new KAction( i18n("&Capture Frame"), "frame_image", Key_C, this, SLOT(captureFrame()), ac, "capture_frame" );
+
+ new KAction( i18n("Video Settings..."), "configure", Key_V, this, SLOT(configure()), ac, "video_settings" );
+ new KAction( i18n("Configure xine..."), "configure", 0, this, SLOT(configure()), ac, "xine_settings" );
+
+ (new KWidgetAction( m_positionSlider, i18n("Position Slider"), 0, 0, 0, ac, "position_slider" ))->setAutoSized( true );
+
+ new VolumeAction( toolBar(), ac );
+}
+
+void
+MainWindow::saveProperties( KConfig *config )
+{
+ config->writeEntry( "url", TheStream::url().url() );
+ config->writeEntry( "time", engine()->time() );
+}
+
+void
+MainWindow::readProperties( KConfig *config )
+{
+ if( engine()->load( config->readPathEntry( "url" ) ) )
+ engine()->play( config->readNumEntry( "time" ) );
+}
+
+void
+MainWindow::timerEvent( QTimerEvent* )
+{
+ 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 ? QString("0%1").arg( n ) : QString::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;
+
+ QString time = zeroPad( s % 60 ); //seconds
+ time.prepend( ':' );
+ time.prepend( zeroPad( m % 60 ) ); //minutes
+ time.prepend( ':' );
+ time.prepend( QString::number( h ) ); //hours
+
+ m_timeLabel->setText( time );
+}
+
+void
+MainWindow::engineMessage( const QString &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 | KIO::UDS_STRING)
+ KIO::UDSEntry e;
+ if (!KIO::NetAccess::stat( url, e, 0 ))
+ MessageBox::sorry( "There was an internal error with the media slave..." );
+ else {
+ KIO::UDSEntry::ConstIterator end = e.end();
+ for (KIO::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 QString 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 : QObject
+{
+ KToolBar *m_toolbar;
+ int m_timer_id;
+ bool m_stay_hidden_for_a_bit;
+ QPoint m_home;
+
+public:
+ FullScreenToolBarHandler( KMainWindow *parent )
+ : QObject( 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( QObject *o, QEvent *e )
+ {
+ if (o == parent() && e->type() == QEvent::MouseMove) {
+ killTimer( m_timer_id );
+
+ QMouseEvent const * const me = (QMouseEvent*)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 = QPoint();
+ 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() == QEvent::Resize)
+ {
+ //we aren't managed by mainWindow when at FullScreen
+ videoWindow()->move( 0, 0 );
+ videoWindow()->resize( ((QWidget*)o)->size() );
+ videoWindow()->lower();
+ }
+
+ if (o == m_toolbar)
+ switch (e->type()) {
+ case QEvent::Enter:
+ m_stay_hidden_for_a_bit = false;
+ killTimer( m_timer_id );
+ break;
+
+ case QEvent::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( QTimerEvent* )
+ {
+ 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 QMainWindow :(
+ setPaletteBackgroundColor( Qt::black ); // due to 2px spacing
+ else
+ toolBar()->unsetPalette(),
+ unsetPalette();
+
+ toolBar()->setMovingEnabled( !isFullScreen );
+ toolBar()->setHidden( isFullScreen && engine()->state() == Engine::Playing );
+
+ reinterpret_cast<QWidget*>(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 ? 0 : videoWindow() );
+}
+
+void
+MainWindow::configure()
+{
+ const QCString 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::setChannels( const QStringList &channels )
+{
+ DEBUG_FUNC_INFO
+
+ //TODO -1 = auto
+
+ QStringList::ConstIterator it = channels.begin();
+
+ QPopupMenu *menu = (QPopupMenu*)child( (*it).latin1() );
+ menu->clear();
+
+ menu->insertItem( i18n("&Determine Automatically"), 1 );
+ menu->insertSeparator();
+
+ //the id is crucial, since the slot this menu is connected to requires
+ //that information to set the correct channel
+ //NOTE we subtract 2 in xineEngine because QMenuData doesn't allow negative id
+ int id = 2;
+ ++it;
+ for( QStringList::ConstIterator const end = channels.end(); it != end; ++it, ++id )
+ menu->insertItem( *it, id );
+
+ menu->insertSeparator();
+ menu->insertItem( i18n("&Off"), 0 );
+
+ id = channels.first() == "subtitle_channels_menu" ? SubtitleChannelsMenuItemId : AudioChannelsMenuItemId;
+ MainWindow::menu( "settings" )->setItemEnabled( id, channels.count() > 1 );
+}
+
+void
+MainWindow::aboutToShowMenu()
+{
+ QPopupMenu *menu = (QPopupMenu*)sender();
+ QCString name( sender() ? sender()->name() : 0 );
+
+ // uncheck all items first
+ for( uint x = 0; x < menu->count(); ++x )
+ menu->setItemChecked( menu->idAt( x ), false );
+
+ int id;
+ if( name == "subtitle_channels_menu" )
+ id = TheStream::subtitleChannel() + 2;
+ else if( name == "audio_channels_menu" )
+ id = TheStream::audioChannel() + 2;
+ else
+ id = TheStream::aspectRatio();
+
+ menu->setItemChecked( id, true );
+}
+
+void
+MainWindow::dragEnterEvent( QDragEnterEvent *e )
+{
+ e->accept( KURLDrag::canDecode( e ) );
+}
+
+void
+MainWindow::dropEvent( QDropEvent *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( QKeyEvent *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 Qt::Key_Left: seek( -500 ); break;
+ case Qt::Key_Right: seek( +500 ); break;
+ case Key_Escape: KWin::clearState( winId(), NET::FullScreen );
+ default: ;
+ }
+
+ #undef seek
+}
+
+QPopupMenu*
+MainWindow::menu( const char *name )
+{
+ // KXMLGUI is "really good".
+ return static_cast<QPopupMenu*>(factory()->container( name, this ));
+}
+
+
+/// Convenience class for other classes that need access to the actionCollection
+KActionCollection*
+actionCollection()
+{
+ return static_cast<MainWindow*>(kapp->mainWidget())->actionCollection();
+}
+
+/// Convenience class for other classes that need access to the actions
+KAction*
+action( const char *name )
+{
+ #define QT_FATAL_ASSERT
+
+ MainWindow *mainWindow = 0;
+ KActionCollection *actionCollection = 0;
+ KAction *action = 0;
+
+ if( mainWindow = (MainWindow*)kapp->mainWidget() )
+ if( actionCollection = mainWindow->actionCollection() )
+ action = actionCollection->action( name );
+
+ Q_ASSERT( mainWindow );
+ Q_ASSERT( actionCollection );
+ Q_ASSERT( action );
+
+ return action;
+}
+
+} //namespace Codeine
diff --git a/src/app/mainWindow.h b/src/app/mainWindow.h
new file mode 100644
index 0000000..63d8468
--- /dev/null
+++ b/src/app/mainWindow.h
@@ -0,0 +1,75 @@
+// (c) 2004 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#ifndef CODEINEMAINWINDOW_H
+#define CODEINEMAINWINDOW_H
+
+#include "codeine.h"
+#include <kmainwindow.h>
+
+class KURL;
+class QLabel;
+class QPopupMenu;
+class QSlider;
+
+
+namespace Codeine
+{
+ class MainWindow : public KMainWindow
+ {
+ Q_OBJECT
+
+ MainWindow();
+ ~MainWindow();
+
+ friend int ::main( int, char** );
+
+ enum { SubtitleChannelsMenuItemId = 2000, AudioChannelsMenuItemId, AspectRatioMenuItemId };
+
+ public slots:
+ void play();
+ void playMedia( bool show_welcome_dialog = false );
+
+ void configure();
+ void streamInformation();
+ void captureFrame();
+
+ private slots:
+ void engineMessage( const QString& );
+ void engineStateChanged( Engine::State );
+ void init();
+ void showTime( int = -1 );
+ void setChannels( const QStringList& );
+ void aboutToShowMenu();
+ void fullScreenToggled( bool );
+
+ private:
+ void setupActions();
+
+ bool load( const KURL& );
+ bool open( const KURL& );
+
+ QPopupMenu *menu( const char *name );
+
+ virtual void timerEvent( QTimerEvent* );
+ virtual void dragEnterEvent( QDragEnterEvent* );
+ virtual void dropEvent( QDropEvent* );
+ virtual void keyPressEvent( QKeyEvent* );
+
+ virtual void saveProperties( KConfig* );
+ virtual void readProperties( KConfig* );
+
+ virtual bool queryExit();
+
+ QSlider *m_positionSlider;
+ QLabel *m_timeLabel;
+ QLabel *m_titleLabel;
+ QWidget *m_analyzer;
+
+ //undefined
+ MainWindow( const MainWindow& );
+ MainWindow &operator=( const MainWindow& );
+ };
+}
+
+#endif
diff --git a/src/app/playDialog.cpp b/src/app/playDialog.cpp
new file mode 100644
index 0000000..50a9ca2
--- /dev/null
+++ b/src/app/playDialog.cpp
@@ -0,0 +1,114 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#include "config.h"
+#include "listView.cpp"
+#include <kapplication.h>
+#include <kconfig.h>
+#include <kguiitem.h>
+#include <klistview.h>
+#include <kpushbutton.h>
+#include <kstdguiitem.h>
+#include "playDialog.h"
+#include "mxcl.library.h"
+#include <qfile.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qsignalmapper.h>
+
+QString i18n( const char *text );
+
+
+namespace Codeine {
+
+
+PlayDialog::PlayDialog( QWidget *parent, bool be_welcome_dialog )
+ : QDialog( parent )
+{
+ setCaption( kapp->makeStdCaption( i18n("Play Media") ) );
+
+ QSignalMapper *mapper = new QSignalMapper( this );
+ QWidget *o, *closeButton = new KPushButton( KStdGuiItem::close(), this );
+ QBoxLayout *hbox, *vbox = new QVBoxLayout( this, 15, 20 );
+
+ vbox->addWidget( new QLabel( i18n( "What media would you like to play?" ), this ) );
+
+ QGridLayout *grid = new QGridLayout( vbox, 1, 3, 20 );
+
+ //TODO use the kguiItems from the actions
+ mapper->setMapping( o = new KPushButton( KGuiItem( i18n("Play File..."), "fileopen" ), this ), FILE );
+ connect( o, SIGNAL(clicked()), mapper, SLOT(map()) );
+ grid->QLayout::add( o );
+
+ mapper->setMapping( o = new KPushButton( KGuiItem( i18n("Play VCD"), "cdaudio_unmount" ), this ), VCD );
+ connect( o, SIGNAL(clicked()), mapper, SLOT(map()) );
+ grid->QLayout::add( o );
+
+ mapper->setMapping( o = new KPushButton( KGuiItem( i18n("Play DVD"), "dvd_unmount" ), this ), DVD );
+ connect( o, SIGNAL(clicked()), mapper, SLOT(map()) );
+ grid->QLayout::add( o );
+
+ mapper->setMapping( closeButton, QDialog::Rejected );
+ connect( closeButton, SIGNAL(clicked()), mapper, SLOT(map()) );
+
+ createRecentFileWidget( vbox );
+
+ hbox = new QHBoxLayout( vbox );
+ hbox->addItem( new QSpacerItem( 10, 10, QSizePolicy::Expanding ) );
+
+ if( be_welcome_dialog ) {
+ QWidget *w = new KPushButton( KStdGuiItem::quit(), this );
+ hbox->addWidget( w );
+ connect( w, SIGNAL(clicked()), kapp, SLOT(quit()) );
+ }
+
+ hbox->addWidget( closeButton );
+
+ connect( mapper, SIGNAL(mapped( int )), SLOT(done( int )) );
+}
+
+void
+PlayDialog::createRecentFileWidget( QBoxLayout *layout )
+{
+ KListView *lv;
+ lv = new Codeine::ListView( this );
+ lv->setColumnText( 1, i18n("Recently Played Media") );
+
+ const QStringList list1 = Codeine::config( "General" )->readPathListEntry( "Recent Urls" );
+ KURL::List urls;
+
+ foreach( list1 )
+ urls += *it;
+
+ for( KURL::List::Iterator it = urls.begin(), end = urls.end(); it != end; ) {
+ if( urls.contains( *it ) > 1 )
+ //remove duplicates
+ it = urls.remove( it );
+ else if( (*it).protocol() == "file" && !QFile::exists( (*it).path() ) )
+ //remove stale entries
+ it = urls.remove( it );
+ else
+ ++it;
+ }
+
+ for( KURL::List::ConstIterator it = urls.begin(), end = urls.end(); it != end; ++it ) {
+ const QString fileName = (*it).fileName();
+ new KListViewItem( lv, 0, (*it).url(), fileName.isEmpty() ? (*it).prettyURL() : fileName );
+ }
+
+ if( lv->childCount() ) {
+ layout->addWidget( lv, 1 );
+ connect( lv, SIGNAL(executed( QListViewItem* )), SLOT(done( QListViewItem* )) );
+ }
+ else
+ delete lv;
+}
+
+void
+PlayDialog::done( QListViewItem *item )
+{
+ m_url = item->text( 0 );
+ QDialog::done( RECENT_FILE );
+}
+
+}
diff --git a/src/app/playDialog.h b/src/app/playDialog.h
new file mode 100644
index 0000000..020f9f1
--- /dev/null
+++ b/src/app/playDialog.h
@@ -0,0 +1,36 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#ifndef CODEINEPLAYDIALOG_H
+#define CODEINEPLAYDIALOG_H
+
+#include <kurl.h>
+#include <qdialog.h>
+
+class KListView;
+class QBoxLayout;
+class QListViewItem;
+
+namespace Codeine
+{
+ class PlayDialog : public QDialog
+ {
+ Q_OBJECT
+ public:
+ PlayDialog( QWidget*, bool show_welcome_dialog = false );
+
+ const KURL &url() const { return m_url; }
+
+ enum DialogCode { FILE = QDialog::Accepted + 2, VCD, CDDA, DVD, RECENT_FILE };
+
+ private slots:
+ void done( QListViewItem* );
+
+ private:
+ void createRecentFileWidget( QBoxLayout* );
+
+ KURL m_url;
+ };
+}
+
+#endif
diff --git a/src/app/playlistFile.cpp b/src/app/playlistFile.cpp
new file mode 100644
index 0000000..19acd30
--- /dev/null
+++ b/src/app/playlistFile.cpp
@@ -0,0 +1,123 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+
+//TODO error messages that vary depending on if the file is remote or not
+
+
+#include "codeine.h"
+#include "debug.h"
+#include <kio/netaccess.h>
+#include "playlistFile.h"
+#include <qfile.h>
+#include <qtextstream.h>
+#include <mxcl.library.h>
+
+
+PlaylistFile::PlaylistFile( const KURL &url )
+ : m_url( url )
+ , m_isRemoteFile( !url.isLocalFile() )
+ , m_isValid( false )
+{
+ mxcl::WaitCursor allocateOnStack;
+
+ QString &path = m_path = url.path();
+
+ if( path.endsWith( ".pls", false ) )
+ m_type = PLS; else
+ if( path.endsWith( ".m3u", false ) )
+ m_type = M3U;
+ else {
+ m_type = Unknown;
+ m_error = i18n( "The file is not a playlist" );
+ return;
+ }
+
+ if( m_isRemoteFile ) {
+ path = QString();
+ if( !KIO::NetAccess::download( url, path, Codeine::mainWindow() ) ) {
+ m_error = i18n( "Codeine could not download the remote playlist: %1" ).arg( url.prettyURL() );
+ return;
+ }
+ }
+
+ QFile file( path );
+ if( file.open( IO_ReadOnly ) ) {
+ QTextStream stream( &file );
+ switch( m_type ) {
+ case M3U: parseM3uFile( stream ); break;
+ case PLS: parsePlsFile( stream ); break;
+ default: ;
+ }
+
+ if( m_contents.isEmpty() )
+ m_error = i18n( "<qt>The playlist, <i>'%1'</i>, could not be interpreted. Perhaps it is empty?" ).arg( path ),
+ m_isValid = false;
+ }
+ else
+ m_error = i18n( "Codeine could not open the file: %1" ).arg( path );
+}
+
+
+PlaylistFile::~PlaylistFile()
+{
+ if( m_isRemoteFile )
+ KIO::NetAccess::removeTempFile( m_path );
+}
+
+
+void
+PlaylistFile::parsePlsFile( QTextStream &stream )
+{
+ DEBUG_BLOCK
+
+ for( QString line = stream.readLine(); !line.isNull(); )
+ {
+ if( line.startsWith( "File" ) ) {
+ const KURL url = line.section( '=', -1 );
+ const QString title = stream.readLine().section( '=', -1 );
+
+ debug() << url << endl << title << endl;
+
+ m_contents += url;
+ m_isValid = true;
+
+ return; //TODO continue for all urls
+ }
+ line = stream.readLine();
+ }
+}
+
+
+void
+PlaylistFile::parseM3uFile( QTextStream &stream )
+{
+ DEBUG_BLOCK
+
+ for( QString line; !stream.atEnd(); )
+ {
+ line = stream.readLine();
+
+ if( line.startsWith( "#EXTINF", false ) )
+ continue;
+
+ else if( !line.startsWith( "#" ) && !line.isEmpty() )
+ {
+ KURL url;
+
+ // KURL::isRelativeURL() expects absolute URLs to start with a protocol, so prepend it if missing
+ if( line.startsWith( "/" ) )
+ line.prepend( "file://" );
+
+ if( KURL::isRelativeURL( line ) )
+ url.setPath( m_url.directory() + line );
+ else
+ url = KURL::fromPathOrURL( line );
+
+ m_contents += url;
+ m_isValid = true;
+
+ return;
+ }
+ }
+}
diff --git a/src/app/playlistFile.h b/src/app/playlistFile.h
new file mode 100644
index 0000000..0302a85
--- /dev/null
+++ b/src/app/playlistFile.h
@@ -0,0 +1,36 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#ifndef CODEINE_PLAYLIST_FILE_H
+#define CODEINE_PLAYLIST_FILE_H
+
+#include <kurl.h>
+
+class PlaylistFile
+{
+public:
+ PlaylistFile( const KURL &url );
+ ~PlaylistFile();
+
+ enum FileFormat { M3U, PLS, Unknown, NotPlaylistFile = Unknown };
+
+ bool isPlaylist() const { return m_type != Unknown; }
+ bool isValid() const { return m_isValid; }
+ KURL firstUrl() const { return m_contents.isEmpty() ? KURL() : m_contents.first(); }
+ QString error() const { return m_error; }
+
+private:
+ /// both only return first url currently
+ void parsePlsFile( QTextStream& );
+ void parseM3uFile( QTextStream& );
+
+ KURL m_url;
+ bool m_isRemoteFile;
+ bool m_isValid;
+ QString m_error;
+ FileFormat m_type;
+ QString m_path;
+ KURL::List m_contents;
+};
+
+#endif
diff --git a/src/app/slider.cpp b/src/app/slider.cpp
new file mode 100644
index 0000000..89b5ced
--- /dev/null
+++ b/src/app/slider.cpp
@@ -0,0 +1,145 @@
+// (c) 2004 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#include "debug.h"
+#include "slider.h"
+#include <qapplication.h>
+#include <qlabel.h>
+#include <qsize.h>
+#include <qtooltip.h>
+
+#include <qpainter.h>
+#include "xineEngine.h"
+
+using Codeine::Slider;
+
+
+Slider *Slider::s_instance = 0;
+
+
+Slider::Slider( QWidget *parent, uint max )
+ : QSlider( Qt::Horizontal, parent )
+ , m_sliding( false )
+ , m_outside( false )
+ , m_prevValue( 0 )
+{
+ s_instance = this;
+
+ setRange( 0, max );
+ setFocusPolicy( NoFocus );
+ setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding );
+}
+
+void
+Slider::wheelEvent( QWheelEvent *e )
+{
+ //if you use this class elsewhere, NOTE this is Codeine specific
+ e->ignore(); //pass to VideoWindow
+}
+
+void
+Slider::mouseMoveEvent( QMouseEvent *e )
+{
+ if( m_sliding )
+ {
+ //feels better, but using set value of 20 is bad of course
+ QRect rect = this->rect();
+ rect.addCoords( -20, -20, 20, 20 );
+
+ if( !rect.contains( e->pos() ) ) {
+ if( !m_outside )
+ QSlider::setValue( m_prevValue );
+ m_outside = true;
+ } else {
+ m_outside = false;
+
+ QSlider::setValue(
+ QRangeControl::valueFromPosition(
+ e->pos().x() - sliderRect().width()/2,
+ width() - sliderRect().width() ) );
+
+ emit sliderMoved( value() );
+ }
+ }
+ else
+ QSlider::mouseMoveEvent( e );
+}
+
+void
+Slider::mousePressEvent( QMouseEvent *e )
+{
+ m_sliding = true;
+ m_prevValue = QSlider::value();
+
+ if( !sliderRect().contains( e->pos() ) )
+ mouseMoveEvent( e );
+}
+
+void
+Slider::mouseReleaseEvent( QMouseEvent* )
+{
+ if( !m_outside && QSlider::value() != m_prevValue )
+ emit sliderReleased( value() );
+
+ m_sliding = false;
+ m_outside = false;
+}
+
+static inline QString timeAsString( const int s )
+{
+ #define zeroPad( n ) n < 10 ? QString("0%1").arg( n ) : QString::number( n )
+ using Codeine::engine;
+
+ const int m = s / 60;
+ const int h = m / 60;
+
+ QString time;
+ time.prepend( zeroPad( s % 60 ) ); //seconds
+ time.prepend( ':' );
+ time.prepend( zeroPad( m % 60 ) ); //minutes
+ time.prepend( ':' );
+ time.prepend( QString::number( h ) ); //hours
+
+ return time;
+}
+
+void
+Slider::setValue( int newValue )
+{
+ static QLabel *w1 = 0;
+ static QLabel *w2 = 0;
+
+ if (!w1) {
+ w1 = new QLabel( this );
+ w1->setPalette( QToolTip::palette() );
+ w1->setFrameStyle( QFrame::Plain | QFrame::Box );
+
+ w2 = new QLabel( this );
+ w2->setPalette( QToolTip::palette() );
+ w2->setFrameStyle( QFrame::Plain | QFrame::Box );
+ }
+
+ //TODO stupidly inefficeint! :)
+ w1->setShown( mainWindow()->isFullScreen() );
+ w2->setShown( mainWindow()->isFullScreen() );
+
+
+ //don't adjust the slider while the user is dragging it!
+
+ if( !m_sliding || m_outside ) {
+ const int l = engine()->length() / 1000;
+ const int left = int(l * (newValue / 65535.0));
+ const int right = l - left;
+
+ QSlider::setValue( newValue );
+ w1->move( 0, height() - w1->height() - 1 );
+ w1->setText( timeAsString( left ) + ' ' );
+ w1->adjustSize();
+
+ w2->move( width() - w2->width(), height() - w1->height() - 1 );
+ w2->setText( timeAsString( right ) + ' ' );
+ w2->adjustSize();
+ }
+ else
+ m_prevValue = newValue;
+}
diff --git a/src/app/slider.h b/src/app/slider.h
new file mode 100644
index 0000000..7e06b6b
--- /dev/null
+++ b/src/app/slider.h
@@ -0,0 +1,52 @@
+// (c) 2004 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#ifndef CODEINESLIDER_H
+#define CODEINESLIDER_H
+
+#include <qslider.h>
+
+namespace Codeine
+{
+ class Slider : public QSlider
+ {
+ Q_OBJECT
+
+ public:
+ static Slider *instance() { return s_instance; }
+
+ public:
+ Slider( QWidget*, uint max = 0 );
+
+ virtual void setValue( int );
+
+ signals:
+ //we emit this when the user has specifically changed the slider
+ //so connect to it if valueChanged() is too generic
+ //Qt also emits valueChanged( int )
+ void sliderReleased( uint );
+
+ protected:
+ virtual void wheelEvent( QWheelEvent* );
+ virtual void mouseMoveEvent( QMouseEvent* );
+ virtual void mouseReleaseEvent( QMouseEvent* );
+ virtual void mousePressEvent( QMouseEvent* );
+ virtual void keyPressEvent( QKeyEvent *e ) { e->ignore(); } //so that MainWindow gets the keypress
+
+ virtual QSize sizeHint() const { return QSlider::sizeHint() + QSize( 0, 6 ); }
+ virtual QSize minimumSizeHint() const { return sizeHint(); }
+
+ bool m_sliding;
+
+ private:
+ static Slider *s_instance;
+
+ bool m_outside;
+ int m_prevValue;
+
+ Slider( const Slider& ); //undefined
+ Slider &operator=( const Slider& ); //undefined
+ };
+}
+
+#endif
diff --git a/src/app/stateChange.cpp b/src/app/stateChange.cpp
new file mode 100644
index 0000000..be15aeb
--- /dev/null
+++ b/src/app/stateChange.cpp
@@ -0,0 +1,195 @@
+// Copyright 2004 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#include "actions.h"
+#include "adjustSizeButton.h"
+#include "debug.h"
+#include "mainWindow.h"
+#include <kconfig.h>
+#include <kglobal.h>
+#include "mxcl.library.h"
+#include <qapplication.h>
+#include <qevent.h>
+#include <qlabel.h>
+#include <qpopupmenu.h>
+#include <qslider.h>
+#include "theStream.h"
+#include "videoSettings.h" //FIXME unfortunate
+#include "xineEngine.h"
+
+
+//TODO do in Sconstruct
+#define QT_FATAL_ASSERT
+
+
+//TODO make the XineEngine into xine::Stream and then make singleton and add functions like Stream::hasVideo() etc.
+//TODO make convenience function to get fullscreen state
+
+
+namespace Codeine {
+
+
+void
+MainWindow::engineStateChanged( Engine::State state )
+{
+ Q_ASSERT( state != Engine::Uninitialised );
+
+ KURL const &url = TheStream::url();
+ bool const isFullScreen = toggleAction("fullscreen")->isChecked();
+ QWidget *const toolbar = reinterpret_cast<QWidget*>(toolBar());
+
+ Debug::Block block( state == Engine::Empty
+ ? "State: Empty" : state == Engine::Loaded
+ ? "State: Loaded" : state == Engine::Playing
+ ? "State: Playing" : state == Engine::Paused
+ ? "State: Paused" : state == Engine::TrackEnded
+ ? "State: TrackEnded" : "State: Unknown" );
+
+
+ /// update actions
+ {
+ using namespace Engine;
+
+ #define enableIf( name, criteria ) action( name )->setEnabled( state & criteria );
+ enableIf( "stop", (Playing | Paused) );
+ enableIf( "fullscreen", (Playing | Paused) );
+ enableIf( "reset_zoom", ~Empty && !isFullScreen );
+ enableIf( "information", ~Empty );
+ enableIf( "video_settings", (Playing | Paused) );
+ enableIf( "volume", (Playing | Paused) );
+ #undef enableIf
+
+ toggleAction( "play" )->setChecked( state == Playing );
+
+ //FIXME bad design to do this way
+ QSlider *volume = (QSlider*)toolBar()->child( "volume" );
+ if (volume)
+ volume->setValue( engine()->volume() );
+ }
+
+
+ /// update VideoSettingsDialog instance
+ VideoSettingsDialog::stateChanged( this, state );
+
+
+ /// update menus
+ {
+ using namespace Engine;
+
+ // the toolbar play button is always enabled, but the menu item
+ // is disabled if we are empty, this looks more sensible
+ QPopupMenu * const file_menu = menu( "file" );
+ QPopupMenu * const settings_menu = menu( "settings" );
+ const int play_id = file_menu->idAt( 2 );
+ file_menu->setItemEnabled( play_id, state != Empty );
+
+ // menus are clearer when handled differently to toolbars
+ // KDE has a shit special action for this, but it stupidly changes
+ // the toolbar icon too.
+ // TODO do this from the playAction since we do it in context menu too
+ const KGuiItem item = (state == Playing) ? KGuiItem( i18n("&Pause"), "player_pause" ) : KGuiItem( i18n("&Play"), "player_play" );
+ file_menu->changeItem( play_id, item.iconSet(), item.text() );
+ file_menu->setItemChecked( play_id, false );
+
+ settings_menu->setItemEnabled( AspectRatioMenuItemId, state & (Playing | Paused) && TheStream::hasVideo() );
+
+ // set correct aspect ratio
+ if( state == Loaded )
+ static_cast<QPopupMenu*>(child( "aspect_ratio_menu" ))->setItemChecked( TheStream::aspectRatio(), true );
+ }
+
+
+ /// update statusBar
+ {
+ using namespace Engine;
+ m_analyzer->setShown( state & (Playing | Paused) && TheStream::hasAudio() );
+ m_timeLabel->setShown( state & (Playing | Paused) );
+ }
+
+
+ /// update position slider
+ switch( state )
+ {
+ case Engine::Empty:
+ m_positionSlider->setEnabled( false );
+ break;
+ case Engine::Loaded:
+ case Engine::TrackEnded:
+ m_positionSlider->setValue( 0 );
+ // NO BREAK!
+ case Engine::Playing:
+ case Engine::Paused:
+ m_positionSlider->setEnabled( TheStream::canSeek() );
+ break;
+ }
+
+
+ /// update recent files list if necessary
+ if( state == Engine::Loaded ) {
+ // update recently played list
+
+ #ifndef NO_SKIP_PR0N
+ // ;-)
+ const QString url_string = url.url();
+ if( !(url_string.contains( "porn", false ) || url_string.contains( "pr0n", false )) )
+ #endif
+ if( url.protocol() != "dvd" && url.protocol() != "vcd" ) {
+ KConfig *config = Codeine::config( "General" );
+ const QString prettyUrl = url.prettyURL();
+
+ QStringList urls = config->readPathListEntry( "Recent Urls" );
+ urls.remove( prettyUrl );
+ config->writePathEntry( "Recent Urls", urls << prettyUrl );
+ }
+
+ if( TheStream::hasVideo() && !isFullScreen )
+ new AdjustSizeButton( reinterpret_cast<QWidget*>(videoWindow()) );
+ }
+
+
+ /// set titles
+ switch( state )
+ {
+ case Engine::Empty:
+ m_titleLabel->setText( i18n("No media loaded") );
+ break;
+ case Engine::Paused:
+ m_titleLabel->setText( i18n("Paused") );
+ break;
+ case Engine::Loaded:
+ case Engine::Playing:
+ case Engine::TrackEnded:
+ m_titleLabel->setText( TheStream::prettyTitle() );
+ break;
+ }
+
+
+ /// set toolbar states
+ QWidget *dvd_button = (QWidget*)toolBar()->child( "toolbutton_toggle_dvd_menu" );
+ if (dvd_button)
+ dvd_button->setShown( state != Engine::Empty && url.protocol() == "dvd" );
+
+ if( isFullScreen && !toolbar->hasMouse() ) {
+ switch( state ) {
+ case Engine::TrackEnded:
+ toolbar->show();
+
+ if( videoWindow()->isActiveWindow() ) {
+ //FIXME dual-screen this seems to still show
+ QContextMenuEvent e( QContextMenuEvent::Other, QPoint(), Qt::MetaButton );
+ QApplication::sendEvent( videoWindow(), &e );
+ }
+ break;
+ case Engine::Empty:
+ case Engine::Loaded:
+ case Engine::Paused:
+ toolBar()->show();
+ break;
+ case Engine::Playing:
+ toolBar()->hide();
+ break;
+ }
+ }
+}
+
+}
diff --git a/src/app/theStream.cpp b/src/app/theStream.cpp
new file mode 100644
index 0000000..5d60d76
--- /dev/null
+++ b/src/app/theStream.cpp
@@ -0,0 +1,144 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#include <kurl.h>
+#include "mxcl.library.h"
+#include "theStream.h"
+#include <xine.h>
+#include "xineEngine.h"
+
+namespace Codeine
+{
+ #define e VideoWindow::s_instance
+
+ KConfig*
+ TheStream::profile()
+ {
+//TODO a unique id for discs, and then even to also record chapters etc.
+// if( url().protocol() == "dvd" )
+// return Codeine::config( QString( "dvd:/" ) + prettyTitle() );
+// else
+ return Codeine::config( url().prettyURL() );
+ }
+
+ const KURL&
+ TheStream::url()
+ { return e->m_url; }
+
+ bool
+ TheStream::canSeek()
+ //FIXME!
+ { return e->m_url.protocol() != "http"; }
+
+ bool
+ TheStream::hasAudio()
+ { return xine_get_stream_info( e->m_stream, XINE_STREAM_INFO_HAS_AUDIO ); }
+
+ bool
+ TheStream::hasVideo()
+ { return xine_get_stream_info( e->m_stream, XINE_STREAM_INFO_HAS_VIDEO ); }
+
+ QSize
+ TheStream::defaultVideoSize()
+ {
+ return !e->m_stream
+ ? QSize()
+ : QSize(
+ xine_get_stream_info( e->m_stream, XINE_STREAM_INFO_VIDEO_WIDTH ),
+ xine_get_stream_info( e->m_stream, XINE_STREAM_INFO_VIDEO_HEIGHT ) );
+ }
+
+ int TheStream::aspectRatio()
+ { return xine_get_param( e->m_stream, XINE_PARAM_VO_ASPECT_RATIO ); }
+
+ int TheStream::subtitleChannel()
+ { return xine_get_param( e->m_stream, XINE_PARAM_SPU_CHANNEL ); }
+
+ int TheStream::audioChannel()
+ { return xine_get_param( e->m_stream, XINE_PARAM_AUDIO_CHANNEL_LOGICAL ); }
+
+ QString
+ TheStream::prettyTitle()
+ {
+ const KURL &url = e->m_url;
+ const QString artist = QString::fromUtf8( xine_get_meta_info( e->m_stream, XINE_META_INFO_ARTIST ) );
+ const QString title = QString::fromUtf8( xine_get_meta_info( e->m_stream, XINE_META_INFO_TITLE ) );
+
+ if (hasVideo() && !title.isEmpty())
+ return title;
+ else if (!title.isEmpty() && !artist.isEmpty())
+ return artist + " - " + title;
+ else if (url.protocol() != "http" && !url.fileName().isEmpty()) {
+ const QString n = url.fileName();
+ return KURL::decode_string( n.left( n.findRev( '.' ) ).replace( '_', ' ' ) ); }
+ else
+ return url.prettyURL();
+ }
+
+
+ static inline QString
+ entryHelper( const QString &plate, const QString &s1, const QString &s2 )
+ {
+ return s2.isEmpty() ? s2 : plate.arg( s1 ).arg( s2 );
+ }
+
+ static inline QString
+ sectionHelper( const QString &sectionTitle, const QStringList &entries )
+ {
+ QString s;
+
+ foreach( entries )
+ if( !(*it).isEmpty() )
+ s += *it;
+
+ return s.isEmpty() ? s : "<h2>" + sectionTitle + "</h2>" + s;
+ }
+
+ QString
+ TheStream::information()
+ {
+ #define meta( x ) xine_get_meta_info( e->m_stream, x )
+ #define info( x, y ) x.arg( xine_get_stream_info( e->m_stream, y ) )
+ #define simple( x ) QString::number( xine_get_stream_info( e->m_stream, x ) )
+
+ const QString plate = "<p><b>%1</b>: %2</p>";
+ QString s;
+
+ s += sectionHelper( i18n("Metadata"),
+ QStringList()
+ << entryHelper( plate, i18n("Title"), meta( XINE_META_INFO_TITLE ) )
+ << entryHelper( plate, i18n("Comment"), meta( XINE_META_INFO_COMMENT ) )
+ << entryHelper( plate, i18n("Artist"), meta( XINE_META_INFO_ARTIST ) )
+ << entryHelper( plate, i18n("Genre"), meta( XINE_META_INFO_GENRE ) )
+ << entryHelper( plate, i18n("Album"), meta( XINE_META_INFO_ALBUM ) )
+ << entryHelper( plate, i18n("Year"), meta( XINE_META_INFO_YEAR ) ) );
+
+ s += sectionHelper( i18n("Audio Properties"),
+ QStringList()
+ << entryHelper( plate, i18n("Bitrate"), info( i18n("%1 bps"), XINE_STREAM_INFO_AUDIO_BITRATE ) )
+ << entryHelper( plate, i18n("Sample-rate"), info( i18n("%1 Hz"), XINE_STREAM_INFO_AUDIO_SAMPLERATE ) ) );
+
+ s += sectionHelper( i18n("Technical Information"),
+ QStringList()
+ << entryHelper( plate, i18n("Video Codec"), meta( XINE_META_INFO_VIDEOCODEC ) )
+ << entryHelper( plate, i18n("Audio Codec"), meta( XINE_META_INFO_AUDIOCODEC ) )
+ << entryHelper( plate, i18n("System Layer"), meta( XINE_META_INFO_SYSTEMLAYER ) )
+ << entryHelper( plate, i18n("Input Plugin"), meta( XINE_META_INFO_INPUT_PLUGIN ))
+ << entryHelper( plate, i18n("CDINDEX_DISCID"), meta( XINE_META_INFO_CDINDEX_DISCID ) ) );
+
+ QStringList texts;
+ texts << "BITRATE" << "SEEKABLE" << "VIDEO_WIDTH" << "VIDEO_HEIGHT" << "VIDEO_RATIO" << "VIDEO_CHANNELS" << "VIDEO_STREAMS" << "VIDEO_BITRATE" << "VIDEO_FOURCC" << "VIDEO_HANDLED" << "FRAME_DURATION" << "AUDIO_CHANNELS" << "AUDIO_BITS" << "-AUDIO_SAMPLERATE" << "-AUDIO_BITRATE" << "AUDIO_FOURCC" << "AUDIO_HANDLED" << "HAS_CHAPTERS" << "HAS_VIDEO" << "HAS_AUDIO" << "-IGNORE_VIDEO" << "-IGNORE_AUDIO" << "-IGNORE_SPU" << "VIDEO_HAS_STILL" << "MAX_AUDIO_CHANNEL" << "MAX_SPU_CHANNEL" << "AUDIO_MODE" << "SKIPPED_FRAMES" << "DISCARDED_FRAMES";
+
+ s += "<h2>Other</h2>";
+ for( uint x = 0; x <= 28; ++x )
+ s += entryHelper( plate, texts[x], simple( x ) );
+
+ #undef meta
+ #undef info
+ #undef simple
+
+ return s;
+ }
+
+ #undef e
+}
diff --git a/src/app/theStream.h b/src/app/theStream.h
new file mode 100644
index 0000000..0ffe64f
--- /dev/null
+++ b/src/app/theStream.h
@@ -0,0 +1,50 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#ifndef CODEINE_THESTREAM_H
+#define CODEINE_THESTREAM_H
+
+#include "config.h" // needed for inline functions
+#include <kurl.h> // larger :( but no macros at least
+#include <qsize.h> // small header
+#include <qstring.h> // small header
+
+/// for purely static classes
+#define CODEINE_NO_EXPORT( T ) \
+ T(); \
+ ~T(); \
+ T( const T& ); \
+ T &operator=( const T& ); \
+ bool operator==( const T& ); \
+ bool operator!=( const T& );
+
+namespace Codeine
+{
+ class TheStream
+ {
+ CODEINE_NO_EXPORT( TheStream )
+
+ public:
+ static const KURL &url();
+
+ static bool canSeek();
+ static bool hasAudio();
+ static bool hasVideo();
+
+ static QSize defaultVideoSize();
+
+ static int aspectRatio();
+ static int subtitleChannel();
+ static int audioChannel();
+
+ static QString prettyTitle();
+ static QString information();
+
+ static inline bool hasProfile()
+ { return KGlobal::config()->hasGroup( url().prettyURL() ); }
+
+ static KConfig *profile();
+ };
+}
+
+#endif
diff --git a/src/app/videoSettings.cpp b/src/app/videoSettings.cpp
new file mode 100644
index 0000000..945e4d3
--- /dev/null
+++ b/src/app/videoSettings.cpp
@@ -0,0 +1,135 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#include <kwin.h>
+#include "mxcl.library.h"
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qslider.h>
+#include "videoSettings.h"
+#include <xine.h>
+#include "xineEngine.h"
+
+extern "C"
+{
+ // #include <X11/Xlib.h> is just dangerous! Here, there is a macro for Below that conflicts
+ // with QSlider::Below. Stupid X11 people.
+ typedef unsigned long XID;
+ typedef XID Window;
+ extern int XSetTransientForHint( Display*, Window, Window );
+}
+
+
+//TODO update from engine when new video is played
+//TODO show a warning that when paused the changes aren't updated to the display, show an unpause button too
+
+
+class SnapSlider : public QSlider
+{
+ int m_offset;
+
+public:
+ SnapSlider( const int value, QWidget *parent, const char *name )
+ : QSlider( (65536/4)-1, (3*(65536/4))-1, 1000, value, Qt::Horizontal, parent, name )
+ , m_offset( 0 )
+ {
+ setTickmarks( QSlider::Below );
+ setTickInterval( 65536 / 4 );
+ setMinimumWidth( fontMetrics().width( name ) * 3 );
+ connect( this, SIGNAL(valueChanged( int )), Codeine::engine(), SLOT(setStreamParameter( int )) );
+ }
+
+ virtual void mousePressEvent( QMouseEvent *e )
+ {
+ m_offset = e->pos().x() - (sliderStart() + (sliderRect().width()/2));
+ QSlider::mousePressEvent( e );
+ }
+
+ virtual void mouseMoveEvent( QMouseEvent *e )
+ {
+ const int MIDDLE = width() / 2;
+ const int x = e->pos().x() - m_offset;
+ const int F = sliderRect().width() / 2;
+
+ if( x > MIDDLE - F && x < MIDDLE + F ) {
+ QMouseEvent e2( e->type(), QPoint( MIDDLE + m_offset, e->pos().y() ), e->button(), e->state() );
+ QSlider::mouseMoveEvent( &e2 );
+ QRangeControl::setValue( 65536 / 2 - 1 ); // to ensure we are absolutely exact
+ }
+ else
+ QSlider::mouseMoveEvent( e );
+ }
+};
+
+
+Codeine::VideoSettingsDialog::VideoSettingsDialog( QWidget *parent )
+ : KDialog( parent, "video_settings_dialog", false, WType_TopLevel | WDestructiveClose )
+{
+ XSetTransientForHint( x11Display(), winId(), parent->winId() );
+ KWin::setType( winId(), NET::Utility );
+ KWin::setState( winId(), NET::SkipTaskbar );
+
+ QFrame *frame = new QFrame( this );
+ (new QVBoxLayout( this, 10 ))->addWidget( frame );
+ frame->setFrameStyle( QFrame::StyledPanel | QFrame::Sunken );
+ frame->setPaletteBackgroundColor( backgroundColor().dark( 102 ) );
+
+ QGridLayout *grid = new QGridLayout( frame, 4, 2, 15, 10 );
+ grid->setAutoAdd( true );
+
+ #define makeSlider( PARAM, name ) \
+ new QLabel( name, frame ); \
+ new SnapSlider( xine_get_param( *Codeine::engine(), PARAM ), frame, name );
+
+ makeSlider( XINE_PARAM_VO_BRIGHTNESS, "brightness" );
+ makeSlider( XINE_PARAM_VO_CONTRAST, "contrast" );
+ makeSlider( XINE_PARAM_VO_SATURATION, "saturation" );
+ makeSlider( XINE_PARAM_VO_HUE, "hue" );
+
+ #undef makeSlider
+
+ setCaption( i18n("Video Settings") );
+ setMaximumSize( sizeHint().width() * 5, sizeHint().height() );
+
+ KDialog::show();
+}
+
+void
+Codeine::VideoSettingsDialog::stateChanged( QWidget *parent, Engine::State state ) //static
+{
+ QWidget *me = (QWidget*)parent->child( "video_settings_dialog" );
+
+ if( !me )
+ return;
+
+ switch( state )
+ {
+ case Engine::Playing:
+ case Engine::Paused:
+ me->setEnabled( true );
+ break;
+
+ case Engine::Loaded:
+ #define update( param, name ) static_cast<QSlider*>(me->child( name ))->setValue( xine_get_param( *Codeine::engine(), param ) );
+ update( XINE_PARAM_VO_BRIGHTNESS, "brightness" );
+ update( XINE_PARAM_VO_CONTRAST, "contrast" );
+ update( XINE_PARAM_VO_SATURATION, "saturation" );
+ update( XINE_PARAM_VO_HUE, "hue" );
+ #undef update
+
+ default:
+ me->setEnabled( false );
+ break;
+ }
+}
+
+namespace Codeine
+{
+ void showVideoSettingsDialog( QWidget *parent )
+ {
+ // ensure that the dialog is shown by deleting the old one
+ delete parent->child( "video_settings_dialog" );
+
+ new VideoSettingsDialog( parent );
+ }
+}
diff --git a/src/app/videoSettings.h b/src/app/videoSettings.h
new file mode 100644
index 0000000..20e01ff
--- /dev/null
+++ b/src/app/videoSettings.h
@@ -0,0 +1,26 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#ifndef CODEINE_VIDEO_SETTINGS_H
+#define CODEINE_VIDEO_SETTINGS_H
+
+#include "codeine.h"
+#include <kdialog.h>
+
+
+namespace Codeine
+{
+ class VideoSettingsDialog : public KDialog
+ {
+ VideoSettingsDialog(); //disable
+ VideoSettingsDialog( const VideoSettingsDialog& ); //disable
+ VideoSettingsDialog &operator=( const VideoSettingsDialog& ); //disable
+
+ public:
+ VideoSettingsDialog( QWidget *parent );
+
+ static void stateChanged( QWidget *parent, Engine::State );
+ };
+}
+
+#endif
diff --git a/src/app/videoWindow.cpp b/src/app/videoWindow.cpp
new file mode 100644
index 0000000..00f2542
--- /dev/null
+++ b/src/app/videoWindow.cpp
@@ -0,0 +1,380 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#define CODEINE_DEBUG_PREFIX "VideoWindow"
+
+#include "actions.h"
+#include <cmath> //std::log10
+#include <cstdlib>
+#include "debug.h"
+#include <kapplication.h> //::makeStandardCaption
+#include <kconfig.h>
+#include <kiconloader.h>
+#include <kpopupmenu.h>
+#include <kwin.h>
+#include "mxcl.library.h"
+#include <qcursor.h>
+#include <qevent.h>
+#include "slider.h"
+#include "theStream.h"
+#include <X11/Xlib.h>
+#include <xine.h>
+#include "xineEngine.h"
+
+
+namespace Codeine
+{
+ namespace X
+ {
+ // we get thread locks if we don't cache these values
+ // (I don't know which ones exactly)
+ Display *d;
+ int s, w;
+ }
+
+
+void
+VideoWindow::initVideo()
+{
+ X::d = XOpenDisplay( std::getenv("DISPLAY") );
+ X::s = DefaultScreen( X::d );
+ X::w = winId();
+
+ XLockDisplay( X::d );
+ XSelectInput( X::d, X::w, ExposureMask );
+
+ {
+ using X::d; using X::s;
+
+ //these are Xlib macros
+ double w = DisplayWidth( d, s ) * 1000 / DisplayWidthMM( d, s );
+ double h = DisplayHeight( d, s ) * 1000 / DisplayHeightMM( d, s );
+
+ m_displayRatio = w / h;
+ }
+
+ connect( &m_timer, SIGNAL(timeout()), SLOT(hideCursor()) );
+
+ XUnlockDisplay( X::d );
+}
+
+void
+VideoWindow::cleanUpVideo()
+{
+ XCloseDisplay( X::d );
+}
+
+void*
+VideoWindow::x11Visual() const
+{
+ DEBUG_FUNC_INFO
+
+ x11_visual_t* visual = new x11_visual_t;
+
+ visual->display = X::d;
+ visual->screen = X::s;
+ visual->d = winId();//X::w;
+ visual->dest_size_cb = &VideoWindow::destSizeCallBack;
+ visual->frame_output_cb = &VideoWindow::frameOutputCallBack;
+ visual->user_data = (void*)this;
+
+ return visual;
+}
+
+void
+VideoWindow::destSizeCallBack(
+ void* p, int /*video_width*/, int /*video_height*/,
+ double /*video_aspect*/, int* dest_width,
+ int* dest_height, double* dest_aspect )
+{
+ if( !p )
+ return;
+
+ #define vw static_cast<VideoWindow*>(p)
+
+ *dest_width = vw->width();
+ *dest_height = vw->height();
+ *dest_aspect = vw->m_displayRatio;
+}
+
+void
+VideoWindow::frameOutputCallBack(
+ void* p, int video_width, int video_height, double video_aspect,
+ int* dest_x, int* dest_y, int* dest_width, int* dest_height,
+ double* dest_aspect, int* win_x, int* win_y )
+{
+ if( !p )
+ return;
+
+ *dest_x = 0;
+ *dest_y = 0 ;
+ *dest_width = vw->width();
+ *dest_height = vw->height();
+ *win_x = vw->x();
+ *win_y = vw->y();
+ *dest_aspect = vw->m_displayRatio;
+
+ // correct size with video aspect
+ // TODO what's this about?
+ if( video_aspect >= vw->m_displayRatio )
+ video_width = (int) ( (double) (video_width * video_aspect / vw->m_displayRatio + 0.5) );
+ else
+ video_height = (int) ( (double) (video_height * vw->m_displayRatio / video_aspect) + 0.5);
+
+ #undef vw
+}
+
+void
+VideoWindow::contextMenuEvent( QContextMenuEvent *e )
+{
+ e->accept();
+
+ KPopupMenu popup;
+
+ if( state() == Engine::Playing )
+ popup.insertItem( SmallIconSet("player_pause"), i18n("Pause"), 1 );
+ else
+ action( "play" )->plug( &popup );
+
+ popup.insertSeparator();
+
+ if( TheStream::url().protocol() == "dvd" )
+ action( "toggle_dvd_menu" )->plug( &popup ),
+ popup.insertSeparator();
+ if( !((KToggleAction*)actionCollection()->action( "fullscreen" ))->isChecked() )
+ action( "reset_zoom" )->plug( &popup );
+ action( "capture_frame" )->plug( &popup );
+ popup.insertSeparator();
+ action( "video_settings" )->plug( &popup );
+ popup.insertSeparator();
+ action( "fullscreen" )->plug( &popup );
+ //show zoom information?
+
+ if( e->state() & Qt::MetaButton ) { //only on track end, or for special users
+ popup.insertSeparator();
+ action( "file_quit" )->plug( &popup );
+ }
+
+ if( popup.exec( e->globalPos() ) == 1 && state() == Engine::Playing )
+ // we check we are still paused as the menu generates a modal event loop
+ // so anything might have happened in the meantime.
+ pause();
+}
+
+bool
+VideoWindow::event( QEvent *e )
+{
+ //TODO it would perhaps make things more responsive to
+ // deactivate mouse tracking and use the x11Event() function to transfer mouse move events?
+ // perhaps even better would be a x11 implementation
+
+ switch( e->type() )
+ {
+ case QEvent::DragEnter:
+ case QEvent::Drop:
+ //FIXME why don't we just ignore the event? It should propogate down
+ return QApplication::sendEvent( qApp->mainWidget(), e );
+
+ case QEvent::Resize:
+ if( !TheStream::url().isEmpty() ) {
+ const QSize defaultSize = TheStream::defaultVideoSize();
+ const bool notDefaultSize = width() != defaultSize.width() && height() != defaultSize.height();
+
+ Codeine::action( "reset_zoom" )->setEnabled( notDefaultSize );
+
+ //showOSD( i18n("Scale: %1%").arg( size()
+ }
+ break;
+
+ case QEvent::Leave:
+ m_timer.stop();
+ break;
+
+ // Xlib.h sucks fucking balls!!!!11!!1!
+ #undef FocusOut
+ case QEvent::FocusOut:
+ // if the user summons some dialog via a shortcut or whatever we need to ensure
+ // the mouse gets shown, because if it is modal, we won't get mouse events after
+ // it is shown! This works because we are always the focus widget.
+ // @see MainWindow::MainWindow where we setFocusProxy()
+ case QEvent::Enter:
+ case QEvent::MouseMove:
+ case QEvent::MouseButtonPress:
+ unsetCursor();
+ if( hasFocus() )
+ // see above comment
+ m_timer.start( CURSOR_HIDE_TIMEOUT, true );
+ break;
+
+ case QEvent::MouseButtonDblClick:
+ Codeine::action( "fullscreen" )->activate();
+ break;
+
+ default: ;
+ }
+
+ if( !m_xine )
+ return QWidget::event( e );
+
+ switch( e->type() )
+ {
+ case QEvent::Close:
+ stop();
+ return false;
+
+ case VideoWindow::ExposeEvent:
+ //see VideoWindow::x11Event()
+
+ return true;
+
+ // Xlib.h sucks fucking balls!!!!11!!1!
+ #undef KeyPress
+ case QEvent::KeyPress: {
+ if( m_url.protocol() != "dvd" )
+ // let MainWindow handle this
+ return QWidget::event( e );
+
+ //FIXME left and right keys don't work during DVDs
+
+ int keyCode = XINE_EVENT_INPUT_UP;
+
+ //#define XINE_EVENT_INPUT_UP 110
+ //#define XINE_EVENT_INPUT_DOWN 111
+ //#define XINE_EVENT_INPUT_LEFT 112
+ //#define XINE_EVENT_INPUT_RIGHT 113
+ //#define XINE_EVENT_INPUT_SELECT 114
+
+ switch( static_cast<QKeyEvent*>(e)->key() ) {
+ case Key_Return:
+ case Key_Enter: keyCode++;
+ case Key_Right: keyCode++;
+ case Key_Left: keyCode++;
+ case Key_Down: keyCode++;
+ case Key_Up:
+ {
+ //this whole shebang is cheeky as xine doesn't
+ //guarentee the codes will stay the same
+
+ xine_event_t xineEvent;
+
+ xineEvent.type = keyCode;
+ xineEvent.data = NULL;
+ xineEvent.data_length = 0;
+
+ xine_event_send( m_stream, &xineEvent );
+
+ return true;
+ }
+ default:
+ return false;
+ }
+ }
+
+ case QEvent::MouseButtonPress:
+
+ #define mouseEvent static_cast<QMouseEvent*>(e)
+
+ if( mouseEvent->button() != Qt::LeftButton )
+ return false;
+
+ mouseEvent->accept();
+
+ //FALL THROUGH
+
+ case QEvent::MouseMove:
+ {
+ x11_rectangle_t x11Rect;
+ xine_event_t xineEvent;
+ xine_input_data_t xineInput;
+
+ x11Rect.x = mouseEvent->x();
+ x11Rect.y = mouseEvent->y();
+ x11Rect.w = 0;
+ x11Rect.h = 0;
+
+ xine_gui_send_vo_data( m_stream, XINE_GUI_SEND_TRANSLATE_GUI_TO_VIDEO, (void*)&x11Rect );
+
+ xineEvent.type = e->type() == QEvent::MouseMove ? XINE_EVENT_INPUT_MOUSE_MOVE : XINE_EVENT_INPUT_MOUSE_BUTTON;
+ xineEvent.data = &xineInput;
+ xineEvent.data_length = sizeof( xine_input_data_t );
+ xineInput.button = 1; //HACK e->type() == QEvent::MouseMove ? 0 : 1;
+ xineInput.x = x11Rect.x;
+ xineInput.y = x11Rect.y;
+ xine_event_send( m_stream, &xineEvent );
+
+ return e->type() == QEvent::MouseMove ? false : true;
+
+ #undef mouseEvent
+ }
+
+ case QEvent::Wheel:
+ {
+ //TODO seek amount should depend on the length, basically seek at most say 30s, and at least 0.5s
+ //TODO this is replicated (somewhat) in MainWindow::keyPressEvent
+
+ int pos, time, length;
+ xine_get_pos_length( m_stream, &pos, &time, &length );
+ pos += int(std::log10( (double)length ) * static_cast<QWheelEvent*>(e)->delta());
+
+ seek( pos > 0 ? (uint)pos : 0 );
+
+ return true;
+ }
+
+ default: ;
+ }
+
+ return QWidget::event( e );
+}
+
+bool
+VideoWindow::x11Event( XEvent *e )
+{
+ if( m_stream && e->type == Expose && e->xexpose.count == 0 ) {
+ xine_gui_send_vo_data(
+ m_stream,
+ XINE_GUI_SEND_EXPOSE_EVENT,
+ e );
+
+ return true;
+ }
+
+ return false;
+}
+
+void
+VideoWindow::hideCursor()
+{
+ setCursor( Qt::BlankCursor );
+}
+
+QSize
+VideoWindow::sizeHint() const //virtual
+{
+ QSize s = TheStream::profile()->readSizeEntry( "Preferred Size" );
+
+ if( !s.isValid() )
+ s = TheStream::defaultVideoSize();
+
+ if( s.isValid() && !s.isNull() )
+ return s;
+
+ return minimumSizeHint();
+}
+
+QSize
+VideoWindow::minimumSizeHint() const //virtual
+{
+ const int x = fontMetrics().width( "x" ) * 4;
+
+ return QSize( x * 12, x * 4 ); //FIXME
+}
+
+void
+VideoWindow::resetZoom()
+{
+ TheStream::profile()->deleteEntry( "Preferred Size" );
+ topLevelWidget()->adjustSize();
+}
+
+} //namespace Codeine
diff --git a/src/app/volumeAction.cpp b/src/app/volumeAction.cpp
new file mode 100644
index 0000000..4215640
--- /dev/null
+++ b/src/app/volumeAction.cpp
@@ -0,0 +1,114 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#include <klocale.h>
+#include <ktoolbar.h>
+#include <qevent.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qslider.h>
+
+#include "debug.h"
+#include "volumeAction.h"
+#include "volumeAction.moc"
+#include "xineEngine.h"
+
+
+class VolumeSlider : public QFrame
+{
+public:
+ VolumeSlider( QWidget *parent )
+ : QFrame( parent )
+ {
+ slider = new QSlider( Qt::Vertical, this, "volume" );
+ label = new QLabel( this );
+
+ QBoxLayout *lay = new QVBoxLayout( this );
+ lay->addWidget( slider, 0, Qt::AlignHCenter );
+ lay->addWidget( label, 0, Qt::AlignHCenter );
+ lay->setMargin( 4 );
+
+ slider->setRange( 0, 100 );
+
+ setFrameStyle( QFrame::Plain | QFrame::Box );
+ setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed );
+
+ hide();
+ }
+
+ QLabel *label;
+ QSlider *slider;
+};
+
+
+VolumeAction::VolumeAction( KToolBar *bar, KActionCollection *ac )
+ : KToggleAction( i18n("Volume"), "volume", Qt::Key_1, 0, 0, ac, "volume" )
+ , m_anchor( 0 )
+{
+ m_widget = new VolumeSlider( bar->topLevelWidget() );
+
+ connect( this, SIGNAL(toggled( bool )), SLOT(toggled( bool )) );
+ connect( m_widget->slider, SIGNAL(sliderMoved( int )), SLOT(sliderMoved( int )) );
+ connect( m_widget->slider, SIGNAL(sliderMoved( int )), Codeine::engine(), SLOT(setStreamParameter( int )) );
+ connect( m_widget->slider, SIGNAL(sliderReleased()), SLOT(sliderReleased()) );
+}
+
+int
+VolumeAction::plug( QWidget *bar, int index )
+{
+ DEBUG_BLOCK
+
+ int const id = KAction::plug( bar, index );
+
+ m_anchor = (QWidget*)bar->child( "toolbutton_volume" ); //KAction creates it with this name
+ m_anchor->installEventFilter( this ); //so we can keep m_widget anchored
+
+ return id;
+}
+
+void
+VolumeAction::toggled( bool const b )
+{
+ DEBUG_BLOCK
+
+ m_widget->raise();
+ m_widget->setShown( b );
+}
+
+void
+VolumeAction::sliderMoved( int v )
+{
+ v = 100 - v; //Qt sliders are wrong way round when vertical
+
+ QString const t = QString::number( v ) + '%';
+
+ setToolTip( i18n( "Volume: %1" ).arg( t ) );
+ m_widget->label->setText( t );
+}
+
+bool
+VolumeAction::eventFilter( QObject *o, QEvent *e )
+{
+ switch (e->type()) {
+ case QEvent::Move:
+ case QEvent::Resize: {
+ QWidget const * const &a = m_anchor;
+
+ m_widget->move( a->mapTo( m_widget->parentWidget(), QPoint( 0, a->height() ) ) );
+ m_widget->resize( a->width(), m_widget->sizeHint().height() );
+ return false;
+ }
+
+ //TODO one click method, flawed currently in fullscreen mode by palette change in mainwindow.cpp
+/* case QEvent::MouseButtonPress:
+ m_widget->show();
+ break;
+
+ case QEvent::MouseButtonRelease:
+ m_widget->hide();
+ break;*/
+
+ default:
+ return false;
+ }
+}
diff --git a/src/app/volumeAction.h b/src/app/volumeAction.h
new file mode 100644
index 0000000..6c0c376
--- /dev/null
+++ b/src/app/volumeAction.h
@@ -0,0 +1,29 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#ifndef CODEINE_VOLUME_ACTION_H
+#define CODEINE_VOLUME_ACTION_H
+
+#include <kactionclasses.h>
+
+class VolumeAction : public KToggleAction
+{
+ Q_OBJECT
+
+ QWidget *m_anchor;
+ class VolumeSlider *m_widget;
+
+ virtual bool eventFilter( QObject *o, QEvent *e );
+
+ virtual int plug( QWidget*, int );
+
+private slots:
+ void toggled( bool );
+ void sliderMoved( int );
+ void sliderReleased() { setChecked( false ); toggled( false ); }
+
+public:
+ VolumeAction( KToolBar *anchor, KActionCollection *ac );
+};
+
+#endif
diff --git a/src/app/xineConfig.cpp b/src/app/xineConfig.cpp
new file mode 100644
index 0000000..70ca11a
--- /dev/null
+++ b/src/app/xineConfig.cpp
@@ -0,0 +1,321 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#include "debug.h"
+#include <kapplication.h> // XineConfigDialog::ctor -> to get the iconloader
+#include <kcombobox.h>
+#include <kiconloader.h> // XineConfigDialog::ctor
+#include <klineedit.h>
+#include <kseparator.h>
+#include <kstdguiitem.h>
+#include <qcheckbox.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qscrollview.h>
+#include <qspinbox.h>
+#include <qtabwidget.h>
+#include <qtooltip.h>
+#include <qvbox.h>
+#include <xine.h>
+#include "xineConfig.h"
+
+QString i18n(const char *text);
+
+
+KDialogBase *XineConfigDialog::s_instance = 0;
+
+
+namespace Codeine
+{
+ void
+ showXineConfigurationDialog( QWidget *parent, xine_t *xine )
+ {
+ XineConfigDialog d( xine, parent );
+ if( d.exec() == QDialog::Accepted )
+ d.saveSettings();
+ }
+}
+
+
+class TabWidget : public QTabWidget
+{
+public:
+ TabWidget( QWidget *parent ) : QTabWidget( parent ) {}
+
+ virtual QSize sizeHint() const
+ {
+ // Qt gives a stupid default sizeHint for this widget
+ return QSize(
+ reinterpret_cast<QWidget*>(tabBar())->sizeHint().width() + 5,
+ QTabWidget::sizeHint().height() );
+ }
+};
+
+
+///@class XineConfigDialog
+
+XineConfigDialog::XineConfigDialog( xine_t *xine, QWidget *parent )
+ : KDialogBase( parent, "xine_config_dialog",
+ true, //modal
+ i18n("Configure xine"), User1 | Stretch | Ok | Cancel,
+ Ok, //default button
+ false, //draw separator
+ KStdGuiItem::reset() )
+ , m_xine( xine )
+{
+ DEBUG_BLOCK
+
+ s_instance = this;
+ const int METRIC = fontMetrics().width( 'x' );
+ const int METRIC_3B2 = (3*METRIC)/2;
+
+ QVBox *box = new QVBox( this );
+ box->setSpacing( METRIC );
+ setMainWidget( box );
+
+ {
+ QHBox *hbox = new QHBox( box );
+ hbox->setSpacing( METRIC_3B2 );
+ hbox->setMargin( METRIC_3B2 );
+ QPixmap info = kapp->iconLoader()->loadIcon( "messagebox_info", KIcon::NoGroup, KIcon::SizeMedium, KIcon::DefaultState, 0, true );
+ QLabel *label = new QLabel( hbox );
+ label->setPixmap( info );
+ label->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Maximum );
+ label = new QLabel( i18n(
+ "xine's defaults are usually sensible and should not require modification. "
+ "However, full configurability is provided for your pleasure ;-)." ), hbox );
+ label->setAlignment( QLabel::WordBreak | QLabel::AlignVCenter );
+ }
+
+ //FIXME after many hours I have discovered that this
+ // widget somehow sets the minSize of this widget to 0,0
+ // whenever you resize the widget. WTF?
+ TabWidget *tabs = new TabWidget( box );
+
+
+ class XineConfigEntryIterator {
+ xine_t *m_xine;
+ xine_cfg_entry_t m_entry;
+ bool m_valid;
+ public:
+ XineConfigEntryIterator( xine_t *xine ) : m_xine( xine ) { m_valid = xine_config_get_first_entry( m_xine, &m_entry ); }
+ inline XineConfigEntryIterator &operator++() { m_valid = xine_config_get_next_entry( m_xine, &m_entry ); return *this; }
+ inline xine_cfg_entry_t *operator*() { return m_valid ? &m_entry : 0; }
+ };
+
+
+ QGridLayout *grid = 0;
+ QString currentPage;
+ QScrollView *view = 0;
+ parent = 0;
+
+ for( XineConfigEntryIterator it( m_xine ); *it; ++it )
+ {
+ const QString pageName = QString::fromUtf8( (*it)->key ).section( '.', 0, 0 );
+
+ if( (QStringList() << "ui" << "effects" << "subtitles").contains( pageName ) )
+ continue;
+
+ if( pageName != currentPage ) {
+ if( view )
+ //NOTE won't be executed for last tab
+ view->viewport()->setMinimumWidth( grid->sizeHint().width() ); // seems necessary
+
+ QString pageTitle = pageName;
+ pageTitle[0] = pageTitle[0].upper();
+
+ tabs->addTab( view = new QScrollView, pageTitle );
+ view->setResizePolicy( QScrollView::AutoOneFit );
+ view->setHScrollBarMode( QScrollView::AlwaysOff );
+ view->setFrameShape( QFrame::NoFrame );
+ view->addChild( parent = new QWidget( view->viewport() ) );
+
+ QBoxLayout *layout = new QVBoxLayout( parent, /*margin*/METRIC_3B2, /*spacing*/0 );
+
+ parent = new QFrame( parent );
+ static_cast<QFrame*>(parent)->setFrameStyle( QFrame::Panel | QFrame::Raised );
+ static_cast<QFrame*>(parent)->setLineWidth( 2 );
+ grid = new QGridLayout( parent, /*rows*/0, /*cols*/2, /*margin*/20, /*spacing*/int(METRIC*2.5) );
+ grid->setColStretch( 0, 3 );
+ grid->setColStretch( 1, 2 );
+
+ layout->addWidget( parent, 0 );
+ layout->addStretch( 1 );
+
+ currentPage = pageName;
+ }
+
+ m_entrys.append( new XineConfigEntry( parent, grid, *it ) );
+ }
+
+ //finishing touches
+ m_entrys.setAutoDelete( true );
+ enableButton( Ok, false );
+ enableButton( User1, false );
+
+ Q_ASSERT( !isUnsavedSettings() );
+}
+
+void
+XineConfigDialog::slotHelp()
+{
+ /// HACK called when a widget's input value changes
+
+ const bool b = isUnsavedSettings();
+ enableButton( Ok, b );
+ enableButton( User1, b );
+}
+
+void
+XineConfigDialog::slotUser1()
+{
+ for( QPtrListIterator<XineConfigEntry> it( m_entrys ); *it != 0; ++it )
+ (*it)->reset();
+
+ slotHelp();
+}
+
+bool
+XineConfigDialog::isUnsavedSettings() const
+{
+ for( QPtrListIterator<XineConfigEntry> it( m_entrys ); *it != 0; ++it )
+ if( (*it)->isChanged() )
+ return true;
+
+ return false;
+}
+
+#include <qdir.h>
+void
+XineConfigDialog::saveSettings()
+{
+ for( XineConfigEntry *entry = m_entrys.first(); entry; entry = m_entrys.next() )
+ if( entry->isChanged() )
+ entry->save( m_xine );
+
+ xine_config_save( m_xine, QFile::encodeName( QDir::homeDirPath() + "/.xine/config" ) );
+}
+
+
+///@class XineConfigEntry
+
+XineConfigEntry::XineConfigEntry( QWidget *parent, QGridLayout *grid, xine_cfg_entry_t *entry )
+ : m_widget( 0 )
+ , m_key( entry->key )
+ , m_string( entry->str_value )
+ , m_number( entry->num_value )
+{
+ QWidget *&w = m_widget;
+ const char *signal = 0;
+ const int row = grid->numRows();
+
+ QString description_text = QString::fromUtf8( entry->description );
+ description_text[0] = description_text[0].upper();
+
+ switch( entry->type )
+ {
+ case XINE_CONFIG_TYPE_STRING: {
+ w = new KLineEdit( m_string, parent );
+ signal = SIGNAL(textChanged( const QString& ));
+ break;
+ }
+ case XINE_CONFIG_TYPE_ENUM: {
+ w = new KComboBox( parent );
+ for( int i = 0; entry->enum_values[i]; ++i )
+ ((KComboBox*)w)->insertItem( QString::fromUtf8( entry->enum_values[i] ) );
+ ((KComboBox*)w)->setCurrentItem( m_number );
+ signal = SIGNAL(activated( int ));
+ break;
+ }
+ case XINE_CONFIG_TYPE_RANGE:
+ case XINE_CONFIG_TYPE_NUM: {
+ w = new QSpinBox(
+ QMIN( m_number, entry->range_min ), // xine bug, sometimes the min and max ranges
+ QMAX( m_number, entry->range_max ), // are both 0 even though this is bullshit
+ 1, parent );
+ ((QSpinBox*)w)->setValue( m_number );
+ signal = SIGNAL(valueChanged( int ));
+ break;
+ }
+ case XINE_CONFIG_TYPE_BOOL: {
+ w = new QCheckBox( description_text, parent );
+ ((QCheckBox*)w)->setChecked( m_number );
+
+ connect( w, SIGNAL(toggled( bool )), XineConfigDialog::instance(), SLOT(slotHelp()) );
+ QToolTip::add( w, "<qt>" + QString::fromUtf8( entry->help ) );
+ grid->addMultiCellWidget( w, row, row, 0, 1 );
+ return; //no need for a description label
+ }
+ default:
+ ;
+ }
+
+ connect( w, signal, XineConfigDialog::instance(), SLOT(slotHelp()) );
+
+ QLabel *description = new QLabel( description_text + ':', parent );
+ description->setAlignment( QLabel::WordBreak | QLabel::AlignVCenter );
+
+ const QString tip = "<qt>" + QString::fromUtf8( entry->help );
+ QToolTip::add( w, tip );
+ QToolTip::add( description, tip );
+
+// grid->addWidget( description, row, 0, Qt::AlignVCenter );
+ grid->addWidget( w, row, 1, Qt::AlignTop );
+}
+
+bool
+XineConfigEntry::isChanged() const
+{
+ #define _( x ) static_cast<x*>(m_widget)
+
+ switch( classType( m_widget->className() ) ) {
+ case LineEdit: return _(KLineEdit)->text().utf8() != m_string;
+ case ComboBox: return _(KComboBox)->currentItem() != m_number;
+ case SpinBox: return _(QSpinBox)->value() != m_number;
+ case CheckBox: return _(QCheckBox)->isChecked() != m_number;
+ }
+ return false;
+}
+
+void
+XineConfigEntry::reset()
+{
+ // this is because we only get called by the XineConfigDialog reset button
+ // and we don't want to cause a check for Ok/Reset button enabled state for
+ // every XineConfigEntry
+ m_widget->blockSignals( true );
+
+ switch( classType( m_widget->className() ) ) {
+ case LineEdit: _(KLineEdit)->setText( m_string ); break;
+ case ComboBox: _(KComboBox)->setCurrentItem( m_number ); break;
+ case SpinBox: _(QSpinBox)->setValue( m_number ); break;
+ case CheckBox: _(QCheckBox)->setChecked( (bool)m_number ); break;
+ }
+ m_widget->blockSignals( false );
+}
+
+void
+XineConfigEntry::save( xine_t *xine )
+{
+ xine_cfg_entry_t ent;
+
+ if( xine_config_lookup_entry( xine, key(), &ent ) )
+ {
+ switch( classType( m_widget->className() ) ) {
+ case LineEdit: m_string = _(KLineEdit)->text().utf8(); break;
+ case ComboBox: m_number = _(KComboBox)->currentItem(); break;
+ case SpinBox: m_number = _(QSpinBox)->value(); break;
+ case CheckBox: m_number = _(QCheckBox)->isChecked(); break;
+ }
+
+ ent.str_value = qstrdup( m_string );
+ ent.num_value = m_number;
+
+ debug() << "Saving setting: " << key() << endl;
+ xine_config_update_entry( xine, &ent );
+ }
+ else
+ Debug::warning() << "Couldn't save: " << key() << endl;
+
+ #undef _
+}
diff --git a/src/app/xineConfig.h b/src/app/xineConfig.h
new file mode 100644
index 0000000..d7999d5
--- /dev/null
+++ b/src/app/xineConfig.h
@@ -0,0 +1,69 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#ifndef XINECONFIG_H
+#define XINECONFIG_H
+
+#include <kdialogbase.h>
+#include <qptrlist.h>
+
+class KComboBox;
+class KLineEdit;
+class QCheckBox;
+class QGridLayout;
+class QSpinBox;
+
+typedef struct xine_s xine_t;
+typedef struct xine_cfg_entry_s xine_cfg_entry_t;
+
+
+///stores a single config entry of the config file
+
+class XineConfigEntry : public QObject
+{
+ enum ClassType { LineEdit, ComboBox, SpinBox, CheckBox };
+
+ QWidget *m_widget;
+ QCString m_key;
+ QCString m_string;
+ int m_number;
+
+ static inline ClassType classType( const QCString &name )
+ {
+ return name == "KLineEdit" ? LineEdit
+ : name == "KComboBox" ? ComboBox
+ : name == "QSpinBox" ? SpinBox : CheckBox;
+ }
+
+public:
+ XineConfigEntry( QWidget *parent, QGridLayout*, xine_cfg_entry_t* );
+
+ bool isChanged() const;
+ void save( xine_t* );
+ void reset();
+
+ inline const QCString &key() const { return m_key; }
+};
+
+
+class XineConfigDialog : public KDialogBase
+{
+ static KDialogBase *s_instance;
+
+ QPtrList<XineConfigEntry> m_entrys;
+ xine_t *m_xine;
+
+public:
+ XineConfigDialog( xine_t *xine, QWidget *parent );
+
+ bool isUnsavedSettings() const;
+ void saveSettings();
+
+ static KDialogBase *instance() { return s_instance; }
+
+protected:
+ virtual void slotUser1();
+ virtual void slotHelp();
+};
+
+#endif
diff --git a/src/app/xineEngine.cpp b/src/app/xineEngine.cpp
new file mode 100644
index 0000000..58069c5
--- /dev/null
+++ b/src/app/xineEngine.cpp
@@ -0,0 +1,876 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#define CODEINE_DEBUG_PREFIX "engine"
+
+#include "actions.h" //::seek() FIXME unfortunate
+#include <cmath> //the fade out
+#include "config.h"
+#include "debug.h"
+#include <limits>
+#include <klocale.h>
+#include "mxcl.library.h"
+#include <qapplication.h> //::sendEvent()
+#include <qdatetime.h> //record()
+#include <qdir.h> //::exists()
+#include "slider.h"
+#include "theStream.h"
+#include <xine.h>
+#include "xineEngine.h"
+#include "xineScope.h"
+
+
+#define XINE_SAFE_MODE 1
+
+extern "C" { void _debug( const char *string ) { debug() << string; } } //FIXME
+
+
+namespace Codeine {
+
+
+VideoWindow *VideoWindow::s_instance = 0;
+
+
+VideoWindow::VideoWindow( QWidget *parent )
+ : QWidget( parent, "VideoWindow" )
+ , m_osd( 0 )
+ , m_stream( 0 )
+ , m_eventQueue( 0 )
+ , m_videoPort( 0 )
+ , m_audioPort( 0 )
+ , m_scope( 0 )
+ , m_xine( 0 )
+ , m_current_vpts( 0 )
+{
+ DEBUG_BLOCK
+
+ s_instance = this;
+
+ setWFlags( Qt::WNoAutoErase );
+ setMouseTracking( true );
+ setAcceptDrops( true );
+ setUpdatesEnabled( false ); //to stop Qt drawing over us
+ setPaletteBackgroundColor( Qt::black );
+ setFocusPolicy( ClickFocus );
+
+ //TODO sucks
+ //TODO namespace this?
+ myList->next = myList; //init the buffer list
+}
+
+VideoWindow::~VideoWindow()
+{
+ DEBUG_BLOCK
+
+ eject();
+
+ // fade out volume on exit
+ if( m_stream && xine_get_status( m_stream ) == XINE_STATUS_PLAY ) {
+ int cum = 0;
+ for( int v = 99; v >= 0; v-- ) {
+ xine_set_param( m_stream, XINE_PARAM_AUDIO_AMP_LEVEL, v );
+ int sleep = int(32000 * (-std::log10( double(v + 1) ) + 2));
+
+ ::usleep( sleep );
+
+ cum += sleep;
+ }
+
+ debug() << "Total sleep: " << cum << "x10^-6 s\n";
+
+ xine_stop( m_stream );
+
+ ::sleep( 1 );
+ }
+
+ //xine_set_param( m_stream, XINE_PARAM_IGNORE_VIDEO, 1 );
+
+ if( m_osd ) xine_osd_free( m_osd );
+ if( m_stream ) xine_close( m_stream );
+ if( m_eventQueue ) xine_event_dispose_queue( m_eventQueue );
+ if( m_stream ) xine_dispose( m_stream );
+ if( m_audioPort ) xine_close_audio_driver( m_xine, m_audioPort );
+ if( m_videoPort ) xine_close_video_driver( m_xine, m_videoPort );
+ if( m_scope ) xine_post_dispose( m_xine, m_scope );
+ if( m_xine ) xine_exit( m_xine );
+
+ cleanUpVideo();
+}
+
+bool
+VideoWindow::init()
+{
+ DEBUG_BLOCK
+
+ initVideo();
+
+ debug() << "xine_new()\n";
+ m_xine = xine_new();
+ if( !m_xine )
+ return false;
+
+ #ifdef XINE_SAFE_MODE
+ xine_engine_set_param( m_xine, XINE_ENGINE_PARAM_VERBOSITY, 99 );
+ #endif
+
+ debug() << "xine_config_load()\n";
+ xine_config_load( m_xine, QFile::encodeName( QDir::homeDirPath() + "/.xine/config" ) );
+
+ debug() << "xine_init()\n";
+ xine_init( m_xine );
+
+ debug() << "xine_open_video_driver()\n";
+ m_videoPort = xine_open_video_driver( m_xine, "auto", XINE_VISUAL_TYPE_X11, videoWindow()->x11Visual() );
+
+ debug() << "xine_open_audio_driver()\n";
+ m_audioPort = xine_open_audio_driver( m_xine, "auto", NULL );
+
+ debug() << "xine_stream_new()\n";
+ m_stream = xine_stream_new( m_xine, m_audioPort, m_videoPort );
+ if( !m_stream )
+ return false;
+
+ // we do these after creating the stream as they are non-fatal
+ // and the messagebox creates a modal event loop that allows
+ // events that require a stream to have been created..
+ if( !m_videoPort )
+ MessageBox::error( i18n("xine was unable to initialize any video-drivers.") );
+ if( !m_audioPort )
+ MessageBox::error( i18n("xine was unable to initialize any audio-drivers.") );
+
+ debug() << "xine_osd_new()\n";
+ m_osd = xine_osd_new( m_stream, 10, 10, 1000, 18 * 6 + 10 );
+ if( m_osd ) {
+ xine_osd_set_font( m_osd, "sans", 18 );
+ xine_osd_set_text_palette( m_osd, XINE_TEXTPALETTE_WHITE_BLACK_TRANSPARENT, XINE_OSD_TEXT1 );
+ }
+
+ #ifndef XINE_SAFE_MODE
+ debug() << "scope_plugin_new()\n";
+ m_scope = scope_plugin_new( m_xine, m_audioPort );
+
+ //FIXME this one seems to make seeking unstable for Codeine, perhaps
+ xine_set_param( m_stream, XINE_PARAM_METRONOM_PREBUFFER, 6000 ); //less buffering, faster seeking..
+
+ // causes an abort currently
+ //xine_trick_mode( m_stream, XINE_TRICK_MODE_SEEK_TO_TIME, 1 );
+ #endif
+
+
+ {
+ typedef QValueList<int> List;
+ List params( List()
+ << XINE_PARAM_VO_HUE << XINE_PARAM_VO_SATURATION << XINE_PARAM_VO_CONTRAST << XINE_PARAM_VO_BRIGHTNESS
+ << XINE_PARAM_SPU_CHANNEL << XINE_PARAM_AUDIO_CHANNEL_LOGICAL << XINE_PARAM_VO_ASPECT_RATIO );
+
+ for( List::ConstIterator it = params.constBegin(), end = params.constEnd(); it != end; ++it )
+ debug1( xine_get_param( m_stream, *it ) );
+ }
+
+
+ debug() << "xine_event_create_listener_thread()\n";
+ xine_event_create_listener_thread( m_eventQueue = xine_event_new_queue( m_stream ), &VideoWindow::xineEventListener, (void*)this );
+
+ //set the UI up to a default state
+ announceStateChange();
+
+ startTimer( 200 ); //prunes the scope
+
+ return true;
+}
+
+void
+VideoWindow::eject()
+{
+ //WARNING! don't xine_stop or that, buggers up dtor
+
+ if( m_url.isEmpty() )
+ return;
+
+ KConfig *profile = TheStream::profile(); // the config profile for this video file
+
+ #define writeParameter( param, default ) { \
+ const int value = xine_get_param( m_stream, param ); \
+ const QString key = QString::number( param ); \
+ if( value != default ) \
+ profile->writeEntry( key, value ); \
+ else \
+ profile->deleteEntry( key ); }
+
+ writeParameter( XINE_PARAM_VO_HUE, 32768 );
+ writeParameter( XINE_PARAM_VO_SATURATION, 32772 );
+ writeParameter( XINE_PARAM_VO_CONTRAST, 32772 );
+ writeParameter( XINE_PARAM_VO_BRIGHTNESS, 32800 )
+ writeParameter( XINE_PARAM_SPU_CHANNEL, -1 );
+ writeParameter( XINE_PARAM_AUDIO_CHANNEL_LOGICAL, -1 );
+ writeParameter( XINE_PARAM_VO_ASPECT_RATIO, 0 );
+
+ #undef writeParameter
+
+
+ if( xine_get_status( m_stream ) == XINE_STATUS_PLAY && //XINE_STATUS_PLAY = playing OR paused
+ length() - time() > 5000 ) // if we are really close to the end, don't remember the position
+ profile->writeEntry( "Position", position() );
+ else
+ profile->deleteEntry( "Position" );
+
+ const QSize s = videoWindow()->size();
+ const QSize defaultSize = TheStream::defaultVideoSize();
+ if( s.width() == defaultSize.width() || s.height() == defaultSize.height() )
+ profile->deleteEntry( "Preferred Size" );
+ else
+ profile->writeEntry( "Preferred Size", s );
+
+ profile->sync();
+
+ m_url = KURL();
+}
+
+bool
+VideoWindow::load( const KURL &url )
+{
+ mxcl::WaitCursor allocateOnStack;
+
+ eject(); //save profile for this video
+
+ m_url = url;
+
+ // only gets shown if there is an error generally, as no event processing
+ // occurs, so no paint event. This is fine IMO, TODO although if xine_open hangs
+ // due to something, it would be good to show the message...
+ emit statusMessage( i18n("Loading media: %1" ).arg( url.fileName() ) );
+
+ debug() << "xine_open()\n";
+ if( xine_open( m_stream, url.url().local8Bit() ) )
+ {
+ KConfig *profile = TheStream::profile();
+ #define setParameter( param, default ) xine_set_param( m_stream, param, profile->readNumEntry( QString::number( param ), default ) );
+ setParameter( XINE_PARAM_VO_HUE, 32768 );
+ setParameter( XINE_PARAM_VO_SATURATION, 32772 );
+ setParameter( XINE_PARAM_VO_CONTRAST, 32772 );
+ setParameter( XINE_PARAM_VO_BRIGHTNESS, 32800 )
+ setParameter( XINE_PARAM_SPU_CHANNEL, -1 );
+ setParameter( XINE_PARAM_AUDIO_CHANNEL_LOGICAL, -1 );
+ setParameter( XINE_PARAM_VO_ASPECT_RATIO, 0 );
+ setParameter( XINE_PARAM_AUDIO_AMP_LEVEL, 100 );
+ #undef setParameter
+
+ videoWindow()->setShown( xine_get_stream_info( m_stream, XINE_STREAM_INFO_HAS_VIDEO ) );
+
+ //TODO popup message for no audio
+ //TODO popup message for no video + no audio
+
+ #ifndef XINE_SAFE_MODE
+ // ensure old buffers are deleted
+ // FIXME leaves one erroneous buffer
+ timerEvent( 0 );
+
+ if( m_scope ) {
+ xine_post_out_t *source = xine_get_audio_source( m_stream );
+ xine_post_in_t *target = (xine_post_in_t*)xine_post_input( m_scope, const_cast<char*>("audio in") );
+ xine_post_wire( source, target );
+ }
+ #endif
+
+ announceStateChange();
+
+ return true;
+ }
+
+ showErrorMessage();
+ announceStateChange();
+ m_url = KURL();
+ return false;
+}
+
+bool
+VideoWindow::play( uint offset )
+{
+ mxcl::WaitCursor allocateOnStack;
+
+ const bool resume = offset > 0 && /*FIXME*/ m_url.protocol() != "dvd";
+ if( resume )
+ //HACK because we have to do xine_play() the audio "stutters"
+ // so we mute it and then unmute it to make it sound better
+ xine_set_param( m_stream, XINE_PARAM_AUDIO_AMP_MUTE, 1 );
+
+ debug() << "xine_play()\n";
+ if( xine_play( m_stream, offset, 0 ) )
+ {
+ if( resume ) {
+ //we have to set this or it stays at 0
+ Slider::instance()->setValue( offset );
+
+ // we come up paused if we are resuming playback from a previous session
+ pause();
+
+ // see above from HACK
+ xine_set_param( m_stream, XINE_PARAM_AUDIO_AMP_MUTE, 0 );
+ }
+ else
+ announceStateChange();
+
+ return true;
+ }
+
+ showErrorMessage();
+ return false;
+}
+
+void
+VideoWindow::record()
+{
+ xine_cfg_entry_t config;
+
+ if( xine_config_lookup_entry( m_xine, "misc.save_dir", &config ) )
+ {
+ //TODO which fricking KDE function tells me this? Who can tell, stupid KDE API
+ QDir d( QDir::home().filePath( "Desktop" ) );
+ config.str_value = qstrdup( d.exists() //FIXME tiny-mem-leak, *shrug*
+ ? d.path().utf8()
+ : QDir::homeDirPath().utf8() );
+ xine_config_update_entry( m_xine, &config );
+
+ const QString fileName = m_url.filename();
+
+ QString
+ url = m_url.url();
+ url += "#save:";
+ url += m_url.host();
+ url += " [";
+ url += QDate::currentDate().toString();
+ url += ']';
+ url += fileName.mid( fileName.findRev( '.' ) + 1 ).lower();
+
+ xine_open( m_stream, url.local8Bit() );
+ xine_play( m_stream, 0, 0 );
+
+ emit statusMessage( i18n( "Recording to: %1" ).arg( url ) );
+
+ debug() << url << endl;
+ }
+ else
+ debug() << "unable to set misc.save_dir\n";
+}
+
+void
+VideoWindow::stop()
+{
+ xine_stop( m_stream );
+
+ announceStateChange();
+}
+
+void
+VideoWindow::pause()
+{
+ if( xine_get_status( m_stream ) == XINE_STATUS_STOP )
+ play();
+
+ else if( m_url.protocol() == "http" )
+ // we are playing and it's an HTTP stream
+ stop();
+
+ else if( xine_get_param( m_stream, XINE_PARAM_SPEED ) ) {
+ // do first because xine is slow to pause and is bad feedback otherwise
+ emit stateChanged( Engine::Paused );
+ xine_set_param( m_stream, XINE_PARAM_SPEED, XINE_SPEED_PAUSE );
+ xine_set_param( m_stream, XINE_PARAM_AUDIO_CLOSE_DEVICE, 1);
+ showOSD( i18n( "Playback paused" ) );
+ }
+ else {
+ xine_set_param( m_stream, XINE_PARAM_SPEED, XINE_SPEED_NORMAL );
+ announceStateChange();
+ showOSD( i18n( "Playback resumed" ) );
+ }
+}
+
+void
+VideoWindow::showErrorMessage()
+{
+ const QString name = m_url.fileName();
+
+ debug() << "xine_get_error()\n";
+ switch( xine_get_error( m_stream ) )
+ {
+ case XINE_ERROR_NO_INPUT_PLUGIN:
+ MessageBox::sorry( i18n("There is no input plugin that can read: %1.").arg( name ) );
+ break;
+ case XINE_ERROR_NO_DEMUX_PLUGIN:
+ MessageBox::sorry( i18n("There is no demux plugin available for %1.").arg( name ) );
+ break;
+ case XINE_ERROR_DEMUX_FAILED:
+ MessageBox::sorry( i18n("Demuxing failed for %1.").arg( name ) );
+ break;
+ case XINE_ERROR_INPUT_FAILED:
+ case XINE_ERROR_MALFORMED_MRL:
+ case XINE_ERROR_NONE:
+ MessageBox::sorry( i18n("Internal error while attempting to play %1.").arg( name ) );
+ break;
+ }
+}
+
+Engine::State
+VideoWindow::state() const
+{
+ //FIXME this is for the analyzer, but I don't like the analyzer being dodgy like this
+ if( !m_xine || !m_stream )
+ return Engine::Uninitialised;
+
+ switch( xine_get_status( m_stream ) )
+ {
+ case XINE_STATUS_PLAY: return xine_get_param( m_stream, XINE_PARAM_SPEED ) ? Engine::Playing : Engine::Paused;
+ case XINE_STATUS_IDLE: return Engine::Empty; //FIXME this route never used!
+ case XINE_STATUS_STOP:
+ default: return m_url.isEmpty() ? Engine::Empty : Engine::Loaded;
+ }
+}
+
+uint
+VideoWindow::posTimeLength( PosTimeLength type ) const
+{
+ int pos = 0, time = 0, length = 0;
+ xine_get_pos_length( m_stream, &pos, &time, &length );
+
+ switch( type ) {
+ case Pos: return pos;
+ case Time: return time;
+ case Length: return length;
+ }
+
+ return 0; //--warning
+}
+
+uint
+VideoWindow::volume() const
+{
+ //TODO I don't like the design
+ return xine_get_param( m_stream, XINE_PARAM_AUDIO_AMP_LEVEL );
+}
+
+void
+VideoWindow::seek( uint pos )
+{
+ bool wasPaused = false;
+
+ // If we seek to the end the track ended event is sent, but it is
+ // delayed as it happens in xine-event loop and before that we are
+ // already processing the next seek event (if user uses mouse wheel
+ // or keyboard to seek) and this causes the ui to think video is
+ // stopped but xine is actually playing the track. Tada!
+ // TODO set state based on events from xine only
+ if( pos > 65534 )
+ pos = 65534;
+
+ switch( state() ) {
+ case Engine::Uninitialised:
+ //NOTE should never happen
+ Debug::warning() << "Seek attempt thwarted! xine not initialised!\n";
+ return;
+ case Engine::Empty:
+ Debug::warning() << "Seek attempt thwarted! No media loaded!\n";
+ return;
+ case Engine::Loaded:
+ // then the state is changing and we should announce it
+ play( pos );
+ return;
+ case Engine::Paused:
+ // xine_play unpauses stream if stream was paused
+ // was broken at 1.0.1 still
+ wasPaused = true;
+ xine_set_param( m_stream, XINE_PARAM_AUDIO_AMP_MUTE, 1 );
+ break;
+ default:
+ ;
+ }
+
+ if( !TheStream::canSeek() ) {
+ // for http streaming it is not a good idea to seek as xine freezes
+ // and/or just breaks, this is xine 1.0.1
+ Debug::warning() << "We won't try to seek as the media is not seekable!\n";
+ return;
+ }
+
+ //TODO depend on a version that CAN seek in flacs!
+ if( m_url.path().endsWith( ".flac", false ) ) {
+ emit statusMessage( i18n("xine cannot currently seek in flac media") );
+ return;
+ }
+
+ //better feedback
+ //NOTE doesn't work! I can't tell why..
+ Slider::instance()->QSlider::setValue( pos );
+ Slider::instance()->repaint( false );
+
+ const bool fullscreen = toggleAction("fullscreen")->isChecked();
+ if( fullscreen ) {
+ //TODO don't use OSD (sucks) show slider widget instead
+ QString osd = "[";
+ QChar separator = '|';
+
+ for( uint x = 0, y = int(pos / (65535.0/20.0)); x < 20; x++ ) {
+ if( x > y )
+ separator = '.';
+ osd += separator;
+ }
+ osd += ']';
+
+ xine_osd_clear( m_osd );
+ xine_osd_draw_text( m_osd, 0, 0, osd.utf8(), XINE_OSD_TEXT1 );
+ xine_osd_show( m_osd, 0 );
+ }
+
+ xine_play( m_stream, (int)pos, 0 );
+
+ if( fullscreen )
+ //after xine_play because the hide command uses stream position
+ xine_osd_hide( m_osd, xine_get_current_vpts( m_stream ) + 180000 ); //2 seconds
+
+ if( wasPaused )
+ xine_set_param( m_stream, XINE_PARAM_SPEED, XINE_SPEED_PAUSE ),
+ xine_set_param( m_stream, XINE_PARAM_AUDIO_AMP_MUTE, 0 );
+}
+
+void
+VideoWindow::setStreamParameter( int value )
+{
+ QCString sender = this->sender()->name();
+ int parameter;
+
+ if( sender == "hue" )
+ parameter = XINE_PARAM_VO_HUE;
+ else if( sender == "saturation" )
+ parameter = XINE_PARAM_VO_SATURATION;
+ else if( sender == "contrast" )
+ parameter = XINE_PARAM_VO_CONTRAST;
+ else if( sender == "brightness" )
+ parameter = XINE_PARAM_VO_BRIGHTNESS;
+ else if( sender == "subtitle_channels_menu" )
+ parameter = XINE_PARAM_SPU_CHANNEL,
+ value -= 2;
+ else if( sender == "audio_channels_menu" )
+ parameter = XINE_PARAM_AUDIO_CHANNEL_LOGICAL,
+ value -= 2;
+ else if( sender == "aspect_ratio_menu" )
+ parameter = XINE_PARAM_VO_ASPECT_RATIO;
+ else if( sender == "volume" )
+ parameter = XINE_PARAM_AUDIO_AMP_LEVEL;
+ else
+ return;
+
+ xine_set_param( m_stream, parameter, value );
+}
+
+const Engine::Scope&
+VideoWindow::scope()
+{
+ using Analyzer::SCOPE_SIZE;
+
+ static Engine::Scope scope( SCOPE_SIZE );
+
+ if( xine_get_status( m_stream ) != XINE_STATUS_PLAY )
+ return scope;
+
+ //prune the buffer list and update the m_current_vpts timestamp
+ timerEvent( 0 );
+
+ for( int channels = xine_get_stream_info( m_stream, XINE_STREAM_INFO_AUDIO_CHANNELS ), frame = 0; frame < SCOPE_SIZE; )
+ {
+ MyNode *best_node = 0;
+
+ for( MyNode *node = myList->next; node != myList; node = node->next )
+ if( node->vpts <= m_current_vpts && (!best_node || node->vpts > best_node->vpts) )
+ best_node = node;
+
+ if( !best_node || best_node->vpts_end < m_current_vpts )
+ break;
+
+ int64_t
+ diff = m_current_vpts;
+ diff -= best_node->vpts;
+ diff *= 1<<16;
+ diff /= myMetronom->pts_per_smpls;
+
+ const int16_t*
+ data16 = best_node->mem;
+ data16 += diff;
+
+ diff += diff % channels; //important correction to ensure we don't overflow the buffer
+ diff /= channels;
+
+ int
+ n = best_node->num_frames;
+ n -= diff;
+ n += frame; //clipping for # of frames we need
+
+ if( n > SCOPE_SIZE )
+ n = SCOPE_SIZE; //bounds limiting
+
+ for( int a, c; frame < n; ++frame, data16 += channels ) {
+ for( a = c = 0; c < channels; ++c )
+ a += data16[c];
+
+ a /= channels;
+ scope[frame] = a;
+ }
+
+ m_current_vpts = best_node->vpts_end;
+ m_current_vpts++; //FIXME needs to be done for some reason, or you get situations where it uses same buffer again and again
+ }
+
+ return scope;
+}
+
+void
+VideoWindow::timerEvent( QTimerEvent* )
+{
+ /// here we prune the buffer list regularly
+ #ifndef XINE_SAFE_MODE
+ MyNode * const first_node = myList->next;
+ MyNode const * const list_end = myList;
+
+ m_current_vpts = (xine_get_status( m_stream ) == XINE_STATUS_PLAY)
+ ? xine_get_current_vpts( m_stream )
+ : std::numeric_limits<int64_t>::max();
+
+ for( MyNode *prev = first_node, *node = first_node->next; node != list_end; node = node->next )
+ {
+ // we never delete first_node
+ // this maintains thread-safety
+ if( node->vpts_end < m_current_vpts ) {
+ prev->next = node->next;
+
+ free( node->mem );
+ free( node );
+
+ node = prev;
+ }
+
+ prev = node;
+ }
+ #endif
+}
+
+void
+VideoWindow::customEvent( QCustomEvent *e )
+{
+ switch( e->type() - 2000 ) {
+ case XINE_EVENT_UI_PLAYBACK_FINISHED:
+ emit stateChanged( Engine::TrackEnded );
+ break;
+
+ case XINE_EVENT_FRAME_FORMAT_CHANGE:
+ //TODO not ideal really
+ debug() << "XINE_EVENT_FRAME_FORMAT_CHANGE\n";
+ break;
+
+ case XINE_EVENT_UI_CHANNELS_CHANGED:
+ {
+ char s[128]; //apparently sufficient
+
+ {
+ QStringList languages( "subtitle_channels_menu" );
+ int channels = xine_get_stream_info( m_stream, XINE_STREAM_INFO_MAX_SPU_CHANNEL );
+ for( int j = 0; j < channels; j++ )
+ languages += xine_get_spu_lang( m_stream, j, s ) ? s : i18n("Channel %1").arg( j+1 );
+ emit channelsChanged( languages );
+ }
+
+ {
+ QStringList languages( "audio_channels_menu" );
+ int channels = xine_get_stream_info( m_stream, XINE_STREAM_INFO_MAX_AUDIO_CHANNEL );
+ for( int j = 0; j < channels; j++ )
+ languages += xine_get_audio_lang( m_stream, j, s ) ? s : i18n("Channel %1").arg( j+1 );
+ emit channelsChanged( languages );
+ }
+ break;
+ }
+
+ case 1000:
+ #define message static_cast<QString*>(e->data())
+ emit statusMessage( *message );
+ delete message;
+ break;
+
+ case 1001:
+ MessageBox::sorry( (*message).arg( m_url.prettyURL() ) );
+ delete message;
+ break;
+
+ case 1002:
+ emit titleChanged( *message );
+ delete message;
+ break;
+ #undef message
+
+ default:
+ ;
+ }
+}
+
+void
+VideoWindow::xineEventListener( void *p, const xine_event_t* xineEvent )
+{
+ if( !p )
+ return;
+
+ #define engine static_cast<VideoWindow*>(p)
+
+ switch( xineEvent->type ) {
+ case XINE_EVENT_UI_NUM_BUTTONS: debug() << "XINE_EVENT_UI_NUM_BUTTONS\n"; break;
+ case XINE_EVENT_MRL_REFERENCE: {
+ //FIXME this is not the right way, it will have bugs
+ debug() << "XINE_EVENT_MRL_REFERENCE\n";
+ engine->m_url = QString::fromUtf8( ((xine_mrl_reference_data_t*)xineEvent->data)->mrl );
+ QTimer::singleShot( 0, engine, SLOT(play()) );
+ break;
+ }
+ case XINE_EVENT_DROPPED_FRAMES: debug() << "XINE_EVENT_DROPPED_FRAMES\n"; break;
+
+ case XINE_EVENT_UI_PLAYBACK_FINISHED:
+ case XINE_EVENT_FRAME_FORMAT_CHANGE:
+ case XINE_EVENT_UI_CHANNELS_CHANGED:
+ {
+ QCustomEvent *ce;
+ ce = new QCustomEvent( 2000 + xineEvent->type );
+ ce->setData( const_cast<xine_event_t*>(xineEvent) );
+ QApplication::postEvent( engine, ce );
+ break;
+ }
+
+ case XINE_EVENT_UI_SET_TITLE:
+ QApplication::postEvent( engine, new QCustomEvent(
+ QEvent::Type(3002),
+ new QString( QString::fromUtf8( static_cast<xine_ui_data_t*>(xineEvent->data)->str ) ) ) );
+ break;
+
+ case XINE_EVENT_PROGRESS:
+ {
+ xine_progress_data_t* pd = (xine_progress_data_t*)xineEvent->data;
+
+ QString
+ msg = "%1 %2%";
+ msg = msg.arg( QString::fromUtf8( pd->description ) )
+ .arg( KGlobal::locale()->formatNumber( pd->percent, 0 ) );
+
+ QApplication::postEvent( engine, new QCustomEvent( QEvent::Type(3000), new QString( msg ) ) );
+ break;
+ }
+ case XINE_EVENT_UI_MESSAGE:
+ {
+ debug() << "message received from xine\n";
+
+ xine_ui_message_data_t *data = (xine_ui_message_data_t *)xineEvent->data;
+ QString message;
+
+ switch( data->type ) {
+ case XINE_MSG_NO_ERROR:
+ {
+ //series of \0 separated strings, terminated with a \0\0
+ char str[2000];
+ char *p = str;
+ for( char *msg = data->messages; !(*msg == '\0' && *(msg+1) == '\0'); ++msg, ++p )
+ *p = *msg == '\0' ? '\n' : *msg;
+ *p = '\0';
+
+ debug() << str << endl;
+
+ break;
+ }
+
+ case XINE_MSG_ENCRYPTED_SOURCE:
+ message = i18n("The source is encrypted and can not be decrypted."); goto param;
+ case XINE_MSG_UNKNOWN_HOST:
+ message = i18n("The host is unknown for the URL: <i>%1</i>"); goto param;
+ case XINE_MSG_UNKNOWN_DEVICE:
+ message = i18n("The device name you specified seems invalid."); goto param;
+ case XINE_MSG_NETWORK_UNREACHABLE:
+ message = i18n("The network appears unreachable."); goto param;
+ case XINE_MSG_AUDIO_OUT_UNAVAILABLE:
+ message = i18n("Audio output unavailable; the device is busy."); goto param;
+ case XINE_MSG_CONNECTION_REFUSED:
+ message = i18n("The connection was refused for the URL: <i>%1</i>"); goto param;
+ case XINE_MSG_FILE_NOT_FOUND:
+ message = i18n("xine could not find the URL: <i>%1</i>"); goto param;
+ case XINE_MSG_PERMISSION_ERROR:
+ message = i18n("Access was denied for the URL: <i>%1</i>"); goto param;
+ case XINE_MSG_READ_ERROR:
+ message = i18n("The source cannot be read for the URL: <i>%1</i>"); goto param;
+ case XINE_MSG_LIBRARY_LOAD_ERROR:
+ message = i18n("A problem occurred while loading a library or decoder."); goto param;
+
+ case XINE_MSG_GENERAL_WARNING:
+ case XINE_MSG_SECURITY:
+ default:
+
+ if(data->explanation)
+ {
+ message += "<b>";
+ message += QString::fromUtf8( (char*) data + data->explanation );
+ message += "</b>";
+ }
+ else break; //if no explanation then why bother!
+
+ //FALL THROUGH
+
+ param:
+
+ message.prepend( "<p>" );
+ message += "<p>";
+
+ if(data->parameters)
+ {
+ message += "xine says: <i>";
+ message += QString::fromUtf8( (char*) data + data->parameters);
+ message += "</i>";
+ }
+ else message += i18n("Sorry, no additional information is available.");
+
+ QApplication::postEvent( engine, new QCustomEvent(QEvent::Type(3001), new QString(message)) );
+ }
+
+ } //case
+ } //switch
+
+ #undef engine
+}
+
+void
+VideoWindow::toggleDVDMenu()
+{
+ xine_event_t e;
+ e.type = XINE_EVENT_INPUT_MENU1;
+ e.data = NULL;
+ e.data_length = 0;
+
+ xine_event_send( m_stream, &e );
+}
+
+void
+VideoWindow::showOSD( const QString &message )
+{
+ if( m_osd ) {
+ xine_osd_clear( m_osd );
+ xine_osd_draw_text( m_osd, 0, 0, message.utf8(), XINE_OSD_TEXT1 );
+ xine_osd_show( m_osd, 0 );
+ xine_osd_hide( m_osd, xine_get_current_vpts( m_stream ) + 180000 ); //2 seconds
+ }
+}
+
+QString
+VideoWindow::fileFilter() const
+{
+ char *supportedExtensions = xine_get_file_extensions( m_xine );
+
+ QString filter( "*." );
+ filter.append( supportedExtensions );
+ filter.remove( "txt" );
+ filter.remove( "png" );
+ filter.replace( ' ', " *." );
+
+ std::free( supportedExtensions );
+
+ return filter;
+}
+
+} //namespace Codeine
diff --git a/src/app/xineEngine.h b/src/app/xineEngine.h
new file mode 100644
index 0000000..781bd72
--- /dev/null
+++ b/src/app/xineEngine.h
@@ -0,0 +1,159 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#ifndef CODEINE_VIDEOWINDOW_H
+#define CODEINE_VIDEOWINDOW_H
+
+#include "codeine.h"
+#include <qtimer.h>
+#include <qwidget.h>
+#include <kurl.h>
+#include <vector>
+
+typedef struct xine_s xine_t;
+typedef struct xine_stream_s xine_stream_t;
+typedef struct xine_video_port_s xine_video_port_t;
+typedef struct xine_audio_port_s xine_audio_port_t;
+typedef struct xine_event_queue_s xine_event_queue_t;
+typedef struct xine_post_s xine_post_t;
+typedef struct xine_osd_s xine_osd_t;
+
+namespace Engine {
+ typedef std::vector<int16_t> Scope;
+}
+
+
+namespace Codeine
+{
+ /** Functions declared here are defined in:
+ * xineEngine.cpp
+ * videoWindow.cpp
+ */
+ class VideoWindow : public QWidget
+ {
+ Q_OBJECT
+
+ enum PosTimeLength { Pos, Time, Length };
+
+ static VideoWindow *s_instance;
+
+ VideoWindow( const VideoWindow& ); //disable
+ VideoWindow &operator=( const VideoWindow& ); //disable
+
+ friend class TheStream;
+ friend VideoWindow* const engine();
+ friend VideoWindow* const videoWindow();
+
+ public:
+ VideoWindow( QWidget *parent );
+ ~VideoWindow();
+
+ bool init();
+ void exit();
+
+ bool load( const KURL &url );
+ bool play( uint = 0 );
+
+ uint position() const { return posTimeLength( Pos ); }
+ uint time() const { return posTimeLength( Time ); }
+ uint length() const { return posTimeLength( Length ); }
+
+ uint volume() const;
+
+ const Engine::Scope &scope();
+ Engine::State state() const;
+
+ operator xine_t*() const { return m_xine; }
+ operator xine_stream_t*() const { return m_stream; }
+
+ public slots:
+ void pause();
+ void record();
+ void seek( uint );
+ void stop();
+
+ ///special slot, see implementation to facilitate understanding
+ void setStreamParameter( int );
+
+ signals:
+ void stateChanged( Engine::State );
+ void statusMessage( const QString& );
+ void titleChanged( const QString& );
+ void channelsChanged( const QStringList& );
+
+ private:
+ #ifdef HAVE_XINE_H
+ static void xineEventListener( void*, const xine_event_t* );
+ #endif
+
+ uint posTimeLength( PosTimeLength ) const;
+ void showErrorMessage();
+
+ virtual void customEvent( QCustomEvent* );
+ virtual void timerEvent( QTimerEvent* );
+
+ void eject();
+
+ void announceStateChange() { emit stateChanged( state() ); }
+
+ xine_osd_t *m_osd;
+ xine_stream_t *m_stream;
+ xine_event_queue_t *m_eventQueue;
+ xine_video_port_t *m_videoPort;
+ xine_audio_port_t *m_audioPort;
+ xine_post_t *m_scope;
+ xine_t *m_xine;
+
+ int64_t m_current_vpts;
+
+ KURL m_url;
+
+ public:
+ QString fileFilter() const;
+
+ public slots:
+ void toggleDVDMenu();
+ void showOSD( const QString& );
+
+ /// Stuff to do with video and the video window/widget
+ private:
+ static void destSizeCallBack( void*, int, int, double, int*, int*, double* );
+ static void frameOutputCallBack( void*, int, int, double, int*, int*, int*, int*, double*, int*, int* );
+
+ void initVideo();
+ void cleanUpVideo();
+
+ public:
+ static const uint CURSOR_HIDE_TIMEOUT = 2000;
+
+ virtual QSize sizeHint() const;
+ virtual QSize minimumSizeHint() const;
+
+ void *x11Visual() const;
+ void becomePreferredSize();
+ QImage captureFrame() const;
+
+ enum { ExposeEvent = 3000 };
+
+ public slots:
+ void resetZoom();
+
+ private slots:
+ void hideCursor();
+
+ private:
+ virtual void contextMenuEvent( QContextMenuEvent* );
+ virtual bool event( QEvent* );
+ virtual bool x11Event( XEvent* );
+
+ double m_displayRatio;
+ QTimer m_timer;
+ };
+
+ //global function for general use by Codeine
+ //videoWindow() is const for Xlib-thread-safety reasons
+ inline VideoWindow* const videoWindow() { return VideoWindow::s_instance; }
+ inline VideoWindow* const engine() { return VideoWindow::s_instance; }
+}
+
+#endif
diff --git a/src/app/xineScope.c b/src/app/xineScope.c
new file mode 100644
index 0000000..740d574
--- /dev/null
+++ b/src/app/xineScope.c
@@ -0,0 +1,148 @@
+/* Author: Max Howell <max.howell@methylblue.com>, (C) 2004
+ Copyright: See COPYING file that comes with this distribution */
+
+/* gcc doesn't like inline for me */
+#define inline
+/* need access to port_ticket */
+#define XINE_ENGINE_INTERNAL
+
+#include "xineScope.h"
+#include <xine/post.h>
+#include <xine/xine_internal.h>
+
+
+static MyNode theList;
+static metronom_t theMetronom;
+static int myChannels = 0;
+
+MyNode* const myList = &theList;
+metronom_t* const myMetronom = &theMetronom;
+
+
+/* defined in xineEngine.cpp */
+extern void _debug( const char * );
+
+
+/*************************
+* post plugin functions *
+*************************/
+
+static int
+scope_port_open( xine_audio_port_t *port_gen, xine_stream_t *stream, uint32_t bits, uint32_t rate, int mode )
+{
+ _debug( "scope_port_open()\n" );
+
+ #define port ((post_audio_port_t*)port_gen)
+
+ _x_post_rewire( (post_plugin_t*)port->post );
+ _x_post_inc_usage( port );
+
+ port->stream = stream;
+ port->bits = bits;
+ port->rate = rate;
+ port->mode = mode;
+
+ myChannels = _x_ao_mode2channels( mode );
+
+ return port->original_port->open( port->original_port, stream, bits, rate, mode );
+}
+
+static void
+scope_port_close( xine_audio_port_t *port_gen, xine_stream_t *stream )
+{
+ _debug( "scope_port_close()\n" );
+
+ port->stream = NULL;
+ port->original_port->close( port->original_port, stream );
+
+ _x_post_dec_usage( port );
+}
+
+static void
+scope_port_put_buffer( xine_audio_port_t *port_gen, audio_buffer_t *buf, xine_stream_t *stream )
+{
+ MyNode *new_node;
+ const int num_samples = buf->num_frames * myChannels;
+
+ /* we are too simple to handle 8bit */
+ /* what does it mean when stream == NULL? */
+ if( port->bits == 8 ) {
+ port->original_port->put_buffer( port->original_port, buf, stream ); return; }
+
+ /* I keep my own metronom because xine wouldn't for some reason */
+ memcpy( myMetronom, stream->metronom, sizeof(metronom_t) );
+
+ new_node = malloc( sizeof(MyNode) );
+ new_node->vpts = myMetronom->got_audio_samples( myMetronom, buf->vpts, buf->num_frames );
+ new_node->num_frames = buf->num_frames;
+ new_node->mem = malloc( num_samples * 2 );
+ memcpy( new_node->mem, buf->mem, num_samples * 2 );
+
+ {
+ int64_t
+ K = myMetronom->pts_per_smpls; /*smpls = 1<<16 samples*/
+ K *= num_samples;
+ K /= (1<<16);
+ K += new_node->vpts;
+
+ new_node->vpts_end = K;
+ }
+
+ /* pass data to original port */
+ port->original_port->put_buffer( port->original_port, buf, stream );
+
+ /* finally we should append the current buffer to the list
+ * NOTE this is thread-safe due to the way we handle the list in the GUI thread */
+ new_node->next = myList->next;
+ myList->next = new_node;
+
+ #undef port
+}
+
+static void
+scope_dispose( post_plugin_t *this )
+{
+ free( this );
+}
+
+
+/************************
+* plugin init function *
+************************/
+
+xine_post_t*
+scope_plugin_new( xine_t *xine, xine_audio_port_t *audio_target )
+{
+ if( audio_target == NULL )
+ return NULL;
+
+ post_plugin_t *post_plugin = xine_xmalloc( sizeof(post_plugin_t) );
+
+ {
+ post_plugin_t *this = post_plugin;
+ post_in_t *input;
+ post_out_t *output;
+ post_audio_port_t *port;
+
+ _x_post_init( this, 1, 0 );
+
+ port = _x_post_intercept_audio_port( this, audio_target, &input, &output );
+ port->new_port.open = scope_port_open;
+ port->new_port.close = scope_port_close;
+ port->new_port.put_buffer = scope_port_put_buffer;
+
+ this->xine_post.audio_input[0] = &port->new_port;
+ this->xine_post.type = PLUGIN_POST;
+
+ this->dispose = scope_dispose;
+ }
+
+ /* code is straight from xine_init_post()
+ can't use that function as it only dlopens the plugins
+ and our plugin is statically linked in */
+
+ post_plugin->running_ticket = xine->port_ticket;
+ post_plugin->xine = xine;
+
+ return &post_plugin->xine_post;
+}
diff --git a/src/app/xineScope.h b/src/app/xineScope.h
new file mode 100644
index 0000000..f2dae75
--- /dev/null
+++ b/src/app/xineScope.h
@@ -0,0 +1,38 @@
+/* Author: Max Howell <max.howell@methylblue.com>, (C) 2004
+ Copyright: See COPYING file that comes with this distribution
+
+ This has to be a c file or for some reason it won't link! (GCC 3.4.1)
+*/
+
+#ifndef XINESCOPE_H
+#define XINESCOPE_H
+
+/* need access to some stuff for scope time stamping */
+#define METRONOM_INTERNAL
+
+#include <sys/types.h>
+#include <xine/metronom.h>
+
+typedef struct my_node_s MyNode;
+
+struct my_node_s
+{
+ MyNode *next;
+ int16_t *mem;
+ int num_frames;
+ int64_t vpts;
+ int64_t vpts_end;
+};
+
+extern metronom_t* const myMetronom;
+extern MyNode* const myList;
+
+#ifdef __cplusplus
+extern "C"
+{
+ xine_post_t*
+ scope_plugin_new( xine_t*, xine_audio_port_t* );
+}
+#endif
+
+#endif