From 5f44f7b187093ef290315b7f8766b540a31de35f Mon Sep 17 00:00:00 2001
From: Michele Calgaro <michele.calgaro@yahoo.it>
Date: Sat, 13 Jun 2020 22:45:28 +0900
Subject: 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>
---
 src/app/SConscript                     |  59 +++
 src/app/actions.cpp                    |  27 +
 src/app/actions.h                      |  26 +
 src/app/adjustSizeButton.cpp           | 125 +++++
 src/app/adjustSizeButton.h             |  37 ++
 src/app/analyzer.cpp                   | 131 +++++
 src/app/analyzer.h                     |  75 +++
 src/app/captureFrame.cpp               | 296 +++++++++++
 src/app/config.h                       |  20 +
 src/app/extern.h                       |  28 ++
 src/app/fht.cpp                        | 262 ++++++++++
 src/app/fht.h                          | 126 +++++
 src/app/fullScreenAction.cpp           |  96 ++++
 src/app/fullScreenAction.h             |  27 +
 src/app/insertAspectRatioMenuItems.cpp |  24 +
 src/app/listView.cpp                   |  39 ++
 src/app/main.cpp                       |  52 ++
 src/app/mainWindow.cpp                 | 714 +++++++++++++++++++++++++++
 src/app/mainWindow.h                   |  75 +++
 src/app/playDialog.cpp                 | 114 +++++
 src/app/playDialog.h                   |  36 ++
 src/app/playlistFile.cpp               | 123 +++++
 src/app/playlistFile.h                 |  36 ++
 src/app/slider.cpp                     | 145 ++++++
 src/app/slider.h                       |  52 ++
 src/app/stateChange.cpp                | 195 ++++++++
 src/app/theStream.cpp                  | 144 ++++++
 src/app/theStream.h                    |  50 ++
 src/app/videoSettings.cpp              | 135 +++++
 src/app/videoSettings.h                |  26 +
 src/app/videoWindow.cpp                | 380 ++++++++++++++
 src/app/volumeAction.cpp               | 114 +++++
 src/app/volumeAction.h                 |  29 ++
 src/app/xineConfig.cpp                 | 321 ++++++++++++
 src/app/xineConfig.h                   |  69 +++
 src/app/xineEngine.cpp                 | 876 +++++++++++++++++++++++++++++++++
 src/app/xineEngine.h                   | 159 ++++++
 src/app/xineScope.c                    | 148 ++++++
 src/app/xineScope.h                    |  38 ++
 39 files changed, 5429 insertions(+)
 create mode 100644 src/app/SConscript
 create mode 100644 src/app/actions.cpp
 create mode 100644 src/app/actions.h
 create mode 100644 src/app/adjustSizeButton.cpp
 create mode 100644 src/app/adjustSizeButton.h
 create mode 100644 src/app/analyzer.cpp
 create mode 100644 src/app/analyzer.h
 create mode 100644 src/app/captureFrame.cpp
 create mode 100644 src/app/config.h
 create mode 100644 src/app/extern.h
 create mode 100644 src/app/fht.cpp
 create mode 100644 src/app/fht.h
 create mode 100644 src/app/fullScreenAction.cpp
 create mode 100644 src/app/fullScreenAction.h
 create mode 100644 src/app/insertAspectRatioMenuItems.cpp
 create mode 100644 src/app/listView.cpp
 create mode 100644 src/app/main.cpp
 create mode 100644 src/app/mainWindow.cpp
 create mode 100644 src/app/mainWindow.h
 create mode 100644 src/app/playDialog.cpp
 create mode 100644 src/app/playDialog.h
 create mode 100644 src/app/playlistFile.cpp
 create mode 100644 src/app/playlistFile.h
 create mode 100644 src/app/slider.cpp
 create mode 100644 src/app/slider.h
 create mode 100644 src/app/stateChange.cpp
 create mode 100644 src/app/theStream.cpp
 create mode 100644 src/app/theStream.h
 create mode 100644 src/app/videoSettings.cpp
 create mode 100644 src/app/videoSettings.h
 create mode 100644 src/app/videoWindow.cpp
 create mode 100644 src/app/volumeAction.cpp
 create mode 100644 src/app/volumeAction.h
 create mode 100644 src/app/xineConfig.cpp
 create mode 100644 src/app/xineConfig.h
 create mode 100644 src/app/xineEngine.cpp
 create mode 100644 src/app/xineEngine.h
 create mode 100644 src/app/xineScope.c
 create mode 100644 src/app/xineScope.h

(limited to 'src/app')

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
-- 
cgit v1.2.1