summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--COPYING340
-rw-r--r--ChangeLog52
-rw-r--r--PKGBUILD17
-rw-r--r--README48
-rw-r--r--SConstruct168
-rw-r--r--VERSION1
-rwxr-xr-xconfigure87
-rw-r--r--misc/codeine.desktop9
-rw-r--r--misc/codeine_part.desktop10
-rw-r--r--misc/codeine_play_dvd.desktop39
-rw-r--r--misc/codeinerc10
-rw-r--r--misc/codeineui.rc33
-rw-r--r--misc/cr128-app-codeine.pngbin0 -> 7426 bytes
-rw-r--r--misc/cr16-app-codeine.pngbin0 -> 870 bytes
-rw-r--r--misc/cr22-app-codeine.pngbin0 -> 1215 bytes
-rw-r--r--misc/cr32-app-codeine.pngbin0 -> 1725 bytes
-rw-r--r--misc/cr48-app-codeine.pngbin0 -> 2689 bytes
-rw-r--r--misc/cr64-app-codeine.pngbin0 -> 3604 bytes
-rw-r--r--po/codeine.pot484
-rwxr-xr-xpo/messages.sh60
-rw-r--r--scons/codeine.py101
-rw-r--r--scons/generic.py95
-rw-r--r--scons/kde.py771
-rw-r--r--scons/scons-mini.tar.bz2bin0 -> 58214 bytes
-rw-r--r--src/FAQ13
-rw-r--r--src/SConscript21
-rw-r--r--src/TODO38
-rw-r--r--src/app/SConscript59
-rw-r--r--src/app/actions.cpp27
-rw-r--r--src/app/actions.h26
-rw-r--r--src/app/adjustSizeButton.cpp125
-rw-r--r--src/app/adjustSizeButton.h37
-rw-r--r--src/app/analyzer.cpp131
-rw-r--r--src/app/analyzer.h75
-rw-r--r--src/app/captureFrame.cpp296
-rw-r--r--src/app/config.h20
-rw-r--r--src/app/extern.h28
-rw-r--r--src/app/fht.cpp262
-rw-r--r--src/app/fht.h126
-rw-r--r--src/app/fullScreenAction.cpp96
-rw-r--r--src/app/fullScreenAction.h27
-rw-r--r--src/app/insertAspectRatioMenuItems.cpp24
-rw-r--r--src/app/listView.cpp39
-rw-r--r--src/app/main.cpp52
-rw-r--r--src/app/mainWindow.cpp714
-rw-r--r--src/app/mainWindow.h75
-rw-r--r--src/app/playDialog.cpp114
-rw-r--r--src/app/playDialog.h36
-rw-r--r--src/app/playlistFile.cpp123
-rw-r--r--src/app/playlistFile.h36
-rw-r--r--src/app/slider.cpp145
-rw-r--r--src/app/slider.h52
-rw-r--r--src/app/stateChange.cpp195
-rw-r--r--src/app/theStream.cpp144
-rw-r--r--src/app/theStream.h50
-rw-r--r--src/app/videoSettings.cpp135
-rw-r--r--src/app/videoSettings.h26
-rw-r--r--src/app/videoWindow.cpp380
-rw-r--r--src/app/volumeAction.cpp114
-rw-r--r--src/app/volumeAction.h29
-rw-r--r--src/app/xineConfig.cpp321
-rw-r--r--src/app/xineConfig.h69
-rw-r--r--src/app/xineEngine.cpp876
-rw-r--r--src/app/xineEngine.h159
-rw-r--r--src/app/xineScope.c148
-rw-r--r--src/app/xineScope.h38
-rw-r--r--src/codeine.h43
-rw-r--r--src/debug.h263
-rw-r--r--src/mxcl.library.cpp19
-rw-r--r--src/mxcl.library.h28
-rw-r--r--src/part/SConscript12
-rw-r--r--src/part/part.cpp83
-rw-r--r--src/part/part.h38
-rw-r--r--src/part/toolbar.cpp44
-rw-r--r--src/part/toolbar.h18
-rw-r--r--src/part/videoWindow.cpp192
-rw-r--r--src/part/videoWindow.h94
-rw-r--r--src/part/xineEngine.cpp345
78 files changed, 9005 insertions, 0 deletions
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..5b6e7c6
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..22fcea1
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,52 @@
+1.0.1
+ Mute button for KPart
+ Play DVD entry for KDE 3.5 + media:/ when DVD inserted
+ DVD-Menu-Toggle is no longer a KToggleAction because I can't detect when DVD menus change, but it still acts as a toggle button
+ Made record work for systems other than mine! (hard-coded path)
+ Made record shortcut CTRL-R so it doesn't conflict with the DVD-Root-Menu toggle
+ videoWindow doesn't judder when toolbar appears in fullscreen anymore
+ dvd-toolbar is gone, instead root menu button appears when dvd is playing
+ toolbar in fullscreen mode shows on mouse move
+ toolbar in fullscreen mode respects user-positioning
+ media kioslave support
+ double-clicking the video toggles fullscreen
+ don't show part in K-Menu
+ a volume toolbar button, - available from the configure-toolbar dialog, it's not very good yet
+
+1.0-rc2
+ Seek fixes
+ Improved error messages
+ KPart crash on exit fix
+
+1.0-beta6
+ Frame capture function
+ Aspect Ratio setting
+ Snap for videoSettings dialog sliders
+ Polish to all dialogs, menus
+ Hide cursor in fullscreen mode bug fixed
+ Many other fixes and lots of polish
+
+1.0-beta3
+ Made Codeine single-window
+ Removed Pause KAction, instead toggling play pauses
+ Made it remember all details about how you like to view videos (eg.
+ contrast, brightness, size)
+ Shows toolbar when mouse is a screen-top in fullscreen mode
+ Bug fixes
+
+1.0-beta2
+ Fixed fullscreen not covering Kicker
+ Added stop KAction
+ Added "You must install!" message after make does linking
+ Set busy cursor during unresponsive init period
+ Made the GUI detect lack of a ui file and show a useful message
+ If you quit during playback, the track volume fades out
+ Various feel/feedback fixes
+ Recent-files list in initial definately doesn't have duplicates anymore, sort order is also corrected
+ Routed out some nasty freeze bugs
+ Automatic frame format change handling
+ Stream recording
+ Many little bug-fixes and improvements
+
+1.0-beta1
+ Initial release
diff --git a/PKGBUILD b/PKGBUILD
new file mode 100644
index 0000000..34bc514
--- /dev/null
+++ b/PKGBUILD
@@ -0,0 +1,17 @@
+pkgname=codeine
+pkgver=1.0.1
+pkgrel=1
+pkgdesc="A simple xine-based video player"
+url="http://www.methylblue.com/codeine/"
+
+build() {
+ echo -e "\033[0;34m==>\033[0;0;1m Configure \033[0;0m"
+ cd "$startdir"
+ ./configure prefix=/opt/kde
+
+ echo -e "\033[0;34m==>\033[0;0;1m Make \033[0;0m"
+ make || return 1
+
+ echo -e "\033[0;34m==>\033[0;0;1m Install \033[0;0m"
+ DESTDIR="$startdir/pkg" make install
+}
diff --git a/README b/README
new file mode 100644
index 0000000..6f5ad6c
--- /dev/null
+++ b/README
@@ -0,0 +1,48 @@
+INTRODUCTION
+ Codeine is a very simple xine-based media player.
+
+ I make the following promises:
+
+ * I will not add any substantial features after version 1.0.0
+ * After then, improvements will only be in the realm of usability and bug
+ fixes
+
+ You can rely on Codeine for now and until xine is obsolete to be a simple
+ no-frills video(/media) player.
+
+ Visit #codeine on freenode.net!
+
+ Max Howell
+
+
+REQUIREMENTS
+ You will need at least:
+
+ * Qt 3.3.0 (Qt 3.2.x may work, it is just not tested)
+ * KDElibs 3.3.0
+ * xine-lib 1.0.0-rc4
+
+ You also need python installed in order to build Codeine.
+
+
+INSTALLATION
+ I use scons + bksys as the build system. But you can still do the following:
+
+ % ./configure && make && su -c "make install"
+
+ Or if you have scons installed, simply:
+
+ % scons && su -c "scons install"
+
+ Note that scons is a little silly and this kind of thing doesn't work:
+
+ % ./configure --prefix=/foo/bar --debug=full
+
+ Instead do:
+
+ % ./configure prefix=/foo/bar debug=full
+
+
+TRANSLATIONS
+ I will make the po file available for translation at the 1.0-rc1 stage, if
+ you want to make a translation I thank you in advance :-)
diff --git a/SConstruct b/SConstruct
new file mode 100644
index 0000000..c08d4dd
--- /dev/null
+++ b/SConstruct
@@ -0,0 +1,168 @@
+#!/usr/bin/python
+
+###########################################
+## Common section, for loading the tools
+
+## Load the builders in config
+env = Environment(TARGS=COMMAND_LINE_TARGETS, ARGS=ARGUMENTS, tools=['default', 'generic', 'kde', 'codeine'], toolpath=['./scons/'])
+
+
+## the configuration should be done by now, quit
+if 'configure' in COMMAND_LINE_TARGETS:
+ env.Exit(0)
+
+
+
+"""
+Overview of the module system :
+
+Each module (kde.py, generic.py, sound.py..) tries to load a stored
+configuration when run. If the stored configuration does not exist
+or if 'configure' is given on the command line (scons configure),
+the module launches the verifications and detectioins and stores
+the results. Modules also call exit when the detection fail.
+
+For example, kde.py stores its config into kde.cache.py
+
+This has several advantages for both developers and users :
+ - Users do not have to run ./configure to compile
+ - The build is insensitive to environment changes
+ - The cache maintains the objects so the config can be changed often
+ - Each module adds its own help via env.Help("message")
+"""
+
+## Use the variables available in the environment - unsafe, but moc, meinproc need it :-/
+import os
+env.AppendUnique( ENV = os.environ )
+## If you do not want to copy the whole environment, you can use this instead (HOME is necessary for uic):
+#env.AppendUnique( ENV = {'PATH' : os.environ['PATH'], 'HOME' : os.environ['HOME']} )
+
+## The target make dist requires the python module shutil which is in 2.3
+env.EnsurePythonVersion(2, 3)
+
+## Bksys requires scons 0.96
+env.EnsureSConsVersion(0, 96)
+
+"""
+Explanation of the 'env = Environment...' line :
+* the command line arguments and targets are stored in env['TARGS'] and env['ARGS'] for use by the tools
+* the part 'tools=['default', 'generic ..' detect and load the necessary functions for doing the things
+* the part "toolpath=['./']" tells that the tools can be found in the current directory (generic.py, kde.py ..)
+"""
+
+"""
+To load more configuration modules one should only have to add the appropriate tool
+ie: to detect alsa and add the proper cflags, ldflags ..
+ a file alsa.py file will be needed, and one should then use :
+ env = Environment(TARGS=COMMAND_LINE_TARGETS, ARGS=ARGUMENTS, tools=['default', 'generic', 'kde', 'alsa'], toolpath=['./'])
+
+You can also load environments that are targetted to different platforms
+ie: if os.sys.platform = "darwin":
+ env = Environment(...
+ elsif os.sys.platform = "linux":
+ env = Environment(...
+
+"""
+
+## Setup the cache directory - this avoids recompiling the same files over and over again
+## this is very handy when working with cvs
+env.CacheDir('cache')
+env.SConsignFile('scons/signatures')
+
+## If you need more libs and they rely on pkg-config
+## ie: add support for GTK (source: the scons wiki on www.scons.org)
+# env.ParseConfig('pkg-config --cflags --libs gtk+-2.0')
+
+"""
+This tell scons that there are no rcs or sccs files - this trick
+can speed up things a bit when having lots of #include
+in the source code and for network file systems
+"""
+env.SourceCode(".", None)
+dirs = [ '.', 'src', 'src/part', 'src/app' ]
+for dir in dirs:
+ env.SourceCode(dir, None)
+
+## If we had only one program (named kvigor) to build,
+## we could add before exporting the env (some kde
+## helpers in kde.py need it) :
+# env['APPNAME'] = 'kvigor'
+
+## Use this define if you are using the kde translation scheme (.po files)
+env.Append( CPPFLAGS = ['-DQT_NO_TRANSLATION'] )
+
+## Uncomment the following if you need threading support threading
+#env.Append( CPPFLAGS = ['-DQT_THREAD_SUPPORT', '-D_REENTRANT'] )
+#if os.uname()[0] == "FreeBSD":
+# env.Append(LINKFLAGS=["-pthread"])
+
+## Important : export the environment so that SConscript files can the
+## configuration and builders in it
+Export("env")
+
+
+def string_it(target, source, env):
+ print "Visit #codeine on irc.freenode.net!"
+ return 0
+
+env.AddPostAction( "install", string_it )
+
+env.SConscript( "src/SConscript", build_dir='build', duplicate=0 )
+
+
+if 'dist' in COMMAND_LINE_TARGETS:
+
+ APPNAME = 'codeine'
+ VERSION = os.popen("cat VERSION").read().rstrip()
+ FOLDER = APPNAME+'-'+VERSION
+ ARCHIVE = FOLDER+'.tar.bz2'
+
+ GREEN ="\033[92m"
+ NORMAL ="\033[0m"
+
+ import shutil
+ import glob
+
+ ## check if the temporary directory already exists
+ if os.path.isdir(FOLDER):
+ shutil.rmtree(FOLDER)
+
+ ## create a temporary directory
+ startdir = os.getcwd()
+ # TODO copying the cache takes forever! delete it first
+ shutil.copytree(startdir, FOLDER)
+
+ ## remove the unnecessary files
+ os.popen("find "+FOLDER+" -name \"{arch}\" | xargs rm -rf")
+ os.popen("find "+FOLDER+" -name \".arch-ids\" | xargs rm -rf")
+ os.popen("find "+FOLDER+" -name \".arch-inventory\" | xargs rm -f")
+ os.popen("find "+FOLDER+" -name \".scon*\" | xargs rm -rf")
+ os.popen("find "+FOLDER+" -name \"kdiss*-data\" | xargs rm -rf")
+ os.popen("find "+FOLDER+" -name \"*.pyc\" | xargs rm -f")
+ os.popen("find "+FOLDER+" -name \"*.cache.py\" | xargs rm -f")
+ os.popen("find "+FOLDER+" -name \"*.log\" | xargs rm -f")
+ os.popen("find "+FOLDER+" -name \"*.kdevelop.*\" | xargs rm -f")
+ os.popen("find "+FOLDER+" -name \"*~\" | xargs rm -f")
+
+ os.popen("rm -rf "+FOLDER+"/autopackage")
+ os.popen("rm -rf "+FOLDER+"/build")
+ os.popen("rm -rf "+FOLDER+"/cache")
+ os.popen("rm -f " +FOLDER+"/codeine-*.tar.bz2")
+ os.popen("rm -f " +FOLDER+"/config.py*")
+ os.popen("rm -f " +FOLDER+"/src/configure.h")
+ os.popen("rm -f " +FOLDER+"/Doxyfile")
+ os.popen("rm -f " +FOLDER+"/Makefile")
+ os.popen("rm -rf "+FOLDER+"/packages")
+ os.popen("rm -rf "+FOLDER+"/screenshots")
+ os.popen("rm -f " +FOLDER+"/scons/signatures.dblite")
+
+ ## make the tarball
+ print GREEN+"Writing archive "+ARCHIVE+NORMAL
+ os.popen("tar cjf "+ARCHIVE+" "+FOLDER)
+
+ ## remove the temporary directory
+ if os.path.isdir(FOLDER):
+ shutil.rmtree(FOLDER)
+
+ env.Default(None)
+ env.Exit(0)
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..7dea76e
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+1.0.1
diff --git a/configure b/configure
new file mode 100755
index 0000000..83b4ab9
--- /dev/null
+++ b/configure
@@ -0,0 +1,87 @@
+#! /bin/sh
+# TODO parse each passed argument and remove any "--" prefix
+
+BOLD="\033[1m"
+RED="\033[91m"
+GREEN="\033[92m"
+YELLOW="\033[93m"
+CYAN="\033[96m"
+NORMAL="\033[0m"
+
+if command -v scons >/dev/null 2>&1;
+then
+ SCONS=scons
+else
+ if [ ! -e "scons/scons" ]; then
+ echo ""
+ echo -ne "Unpacking mini-scons..."$RED
+
+ pushd scons >/dev/null 2>&1
+ tar xjvf scons-mini.tar.bz2 > /dev/null 2>&1
+
+ if [[ "$?" == "0" ]]; then
+ echo -e $GREEN"done"$NORMAL
+ else
+ echo -e $RED"failed!"$NORMAL
+ exit 2
+ fi
+
+ popd > /dev/null
+ fi
+
+ SCONS=scons/scons
+fi
+
+if [[ "$1" == "--help" ]]; then
+ $SCONS -Q configure --help
+ exit
+fi
+
+echo ""
+echo "Configuring Codeine "`cat VERSION`"..."
+echo ""
+
+#TODO remove all prefixed "--"
+
+$SCONS -Q configure $@ || exit 1
+
+echo ""
+echo -e "Your configure completed "$GREEN"successfully"$NORMAL", now type "$BOLD"make"$NORMAL
+echo ""
+
+cat > Makefile << EOF
+## Makefile automatically generated by unpack_local_scons.sh
+
+SCONS=$SCONS
+
+# scons : compile
+# scons -c : clean
+# scons install : install
+# scons -c install : uninstall and clean
+
+# default target : use scons to build the programs
+all:
+ \$(SCONS) -Q
+
+### There are several possibilities to help debugging :
+# scons --debug=explain, scons --debug=tree ..
+#
+### To optimize the runtime, use
+# scons --max-drift=1 --implicit-deps-unchanged
+debug:
+ \$(SCONS) -Q --debug=tree
+
+clean:
+ \$(SCONS) -c
+
+install:
+ \$(SCONS) install
+
+uninstall:
+ \$(SCONS) -c install
+
+## this target creates a tarball of the project
+dist:
+ \$(SCONS) dist
+EOF
+
diff --git a/misc/codeine.desktop b/misc/codeine.desktop
new file mode 100644
index 0000000..5def0f8
--- /dev/null
+++ b/misc/codeine.desktop
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Name=Codeine
+Exec=codeine %u
+Icon=codeine
+Type=Application
+Encoding=UTF-8
+MimeType=video/x-theora;video/x-ogm;video/x-ms-wmv;video/x-msvideo;video/x-ms-asf;video/x-matroska;video/mpeg;video/avi;video/quicktime;video/vnd.rn-realvideo;video/x-flic;
+Categories=Qt;KDE;AudioVideo;Player
+GenericName=Video Player
diff --git a/misc/codeine_part.desktop b/misc/codeine_part.desktop
new file mode 100644
index 0000000..728da15
--- /dev/null
+++ b/misc/codeine_part.desktop
@@ -0,0 +1,10 @@
+[Desktop Entry]
+Encoding=UTF-8
+Icon=codeine
+MimeType=video/x-theora;video/x-ogm;video/x-ms-wmv;video/x-msvideo;video/x-ms-asf;video/x-matroska;video/mpeg;video/avi;video/quicktime;video/vnd.rn-realvideo;video/x-flic;
+Name=Codeine
+Comment=Embeddable Video Player
+ServiceTypes=KParts/ReadOnlyPart
+Type=Service
+X-KDE-Library=libcodeine
+InitialPreference=9
diff --git a/misc/codeine_play_dvd.desktop b/misc/codeine_play_dvd.desktop
new file mode 100644
index 0000000..d1e9989
--- /dev/null
+++ b/misc/codeine_play_dvd.desktop
@@ -0,0 +1,39 @@
+[Desktop Entry]
+ServiceTypes=media/dvdvideo
+Actions=Play;
+Encoding=UTF-8
+X-KDE-Priority=TopLevel
+
+[Desktop Action Play]
+Name=Play DVD with Codeine
+Name[bg]=Възпроизвеждане на DVD с Codeine
+Name[bn]=ক্যাফিন দিয়ে ডিভিডি চালাও
+Name[br]=Seniñ an DVD gant Codeine
+Name[ca]=Reprodueix DVD amb Codeine
+Name[cs]=Přehrát DVD v Codeine
+Name[da]=Spil dvd med Codeine
+Name[de]=DVD mit Codeine abspielen
+Name[el]=Αναπαραγωγή DVD με το Codeine
+Name[es]=Reproducir DVD con Codeine
+Name[et]=Esita DVD Codeine'is
+Name[fi]=Toista dvd-levy Codeinessa
+Name[fr]=Lire le DVD avec Codeine
+Name[ga]=Seinn DVD le Codeine
+Name[he]=נגן תקליטור DVD עם Codeine
+Name[it]=Riproduci DVD con Codeine
+Name[ja]=CodeineでDVDを再生
+Name[nb]=Spill DVD med Codeine
+Name[nl]=DVD met Codeine afspelen
+Name[nn]=Spel DVD med Codeine
+Name[pa]=ਕੈਫੀਨ ਨਾਲ DVD ਚਲਾਓ
+Name[pl]=Odtwarzaj DVD w Codeine
+Name[pt]=Ver o DVD com o Codeine
+Name[pt_BR]=Reproduzir o DVD com o Codeine
+Name[sr]=Пусти DVD Codeine-ом
+Name[sr@Latn]=Pusti DVD Codeine-om
+Name[sv]=Spela dvd med Codeine
+Name[tr]=DVD'yi Codeine ile oynat
+Name[xx]=xxPlay DVD with Codeinexx
+Name[zh_CN]=用 Codeine 播放 DVD
+Icon=codeine
+Exec=codeine --play-dvd %u
diff --git a/misc/codeinerc b/misc/codeinerc
new file mode 100644
index 0000000..399baa9
--- /dev/null
+++ b/misc/codeinerc
@@ -0,0 +1,10 @@
+[KFileDialog Settings]
+ShowPreviews=false
+
+[MainWindow Toolbar dvdToolBar]
+Hidden=true
+IconText=IconTextRight
+Index=0
+
+[MainWindow Toolbar mainToolBar]
+Index=1
diff --git a/misc/codeineui.rc b/misc/codeineui.rc
new file mode 100644
index 0000000..d6ef71f
--- /dev/null
+++ b/misc/codeineui.rc
@@ -0,0 +1,33 @@
+<!DOCTYPE kpartgui>
+<kpartgui name="codeine" version="4">
+<MenuBar>
+ <Menu name="file" noMerge="1"><text>&amp;Play</text>
+ <Action name="play_media"/>
+ <Separator/>
+ <Action name="play"/>
+ <Action name="stop"/>
+ <Separator/>
+ <Action name="file_quit"/>
+ </Menu>
+ <Menu name="settings" noMerge="1"><text>&amp;Settings</text>
+ <Separator/><!-- this seperator doesn't show :( -->
+ <Action name="fullscreen"/>
+ <Separator/><!-- this seperator doesn't show :( -->
+ <Action name="options_configure_keybinding"/>
+ <Action name="options_configure_toolbars"/>
+ <Separator/>
+ <Action name="video_settings"/>
+ <Action name="xine_settings"/>
+ </Menu>
+</MenuBar>
+
+<ToolBar name="mainToolBar"><text>Main Toolbar</text>
+ <Action name="play"/>
+ <Separator lineSeparator="false"/>
+ <Action name="position_slider"/>
+ <Separator lineSeparator="false"/>
+ <Action name="toggle_dvd_menu"/>
+ <Action name="fullscreen"/>
+</ToolBar>
+
+</kpartgui>
diff --git a/misc/cr128-app-codeine.png b/misc/cr128-app-codeine.png
new file mode 100644
index 0000000..f01fd7e
--- /dev/null
+++ b/misc/cr128-app-codeine.png
Binary files differ
diff --git a/misc/cr16-app-codeine.png b/misc/cr16-app-codeine.png
new file mode 100644
index 0000000..dab56c9
--- /dev/null
+++ b/misc/cr16-app-codeine.png
Binary files differ
diff --git a/misc/cr22-app-codeine.png b/misc/cr22-app-codeine.png
new file mode 100644
index 0000000..c8ed199
--- /dev/null
+++ b/misc/cr22-app-codeine.png
Binary files differ
diff --git a/misc/cr32-app-codeine.png b/misc/cr32-app-codeine.png
new file mode 100644
index 0000000..6cbd81c
--- /dev/null
+++ b/misc/cr32-app-codeine.png
Binary files differ
diff --git a/misc/cr48-app-codeine.png b/misc/cr48-app-codeine.png
new file mode 100644
index 0000000..f9f9180
--- /dev/null
+++ b/misc/cr48-app-codeine.png
Binary files differ
diff --git a/misc/cr64-app-codeine.png b/misc/cr64-app-codeine.png
new file mode 100644
index 0000000..da50713
--- /dev/null
+++ b/misc/cr64-app-codeine.png
Binary files differ
diff --git a/po/codeine.pot b/po/codeine.pot
new file mode 100644
index 0000000..47448a6
--- /dev/null
+++ b/po/codeine.pot
@@ -0,0 +1,484 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2005-08-01 17:30+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../src/app/videoSettings.cpp:91
+msgid "Video Settings"
+msgstr ""
+
+#: ../src/app/playDialog.cpp:27
+msgid "Play Media"
+msgstr ""
+
+#: ../src/app/playDialog.cpp:33
+msgid "What media would you like to play?"
+msgstr ""
+
+#: ../src/app/playDialog.cpp:38
+msgid "Play File..."
+msgstr ""
+
+#: ../src/app/playDialog.cpp:42
+msgid "Play VCD"
+msgstr ""
+
+#: ../src/app/playDialog.cpp:46
+msgid "Play DVD"
+msgstr ""
+
+#: ../src/app/playDialog.cpp:73
+msgid "Recently Played Media"
+msgstr ""
+
+#: ../src/app/xineConfig.cpp:60
+msgid "Configure xine"
+msgstr ""
+
+#: ../src/app/xineConfig.cpp:84
+msgid ""
+"xine's defaults are usually sensible and should not require modification. "
+"However, full configurability is provided for your pleasure ;-)."
+msgstr ""
+
+#: ../src/app/adjustSizeButton.cpp:31
+msgid "Preferred Scale"
+msgstr ""
+
+#: ../src/app/adjustSizeButton.cpp:35
+msgid "Scale 100%"
+msgstr ""
+
+#: ../src/app/adjustSizeButton.cpp:41
+msgid "<b>Adjust video scale?"
+msgstr ""
+
+#: ../src/app/insertAspectRatioMenuItems.cpp:15
+msgid "Determine &Automatically"
+msgstr ""
+
+#: ../src/app/insertAspectRatioMenuItems.cpp:17
+msgid "&Square (1:1)"
+msgstr ""
+
+#: ../src/app/insertAspectRatioMenuItems.cpp:18
+msgid "&4:3"
+msgstr ""
+
+#: ../src/app/insertAspectRatioMenuItems.cpp:19
+msgid "Ana&morphic (16:9)"
+msgstr ""
+
+#: ../src/app/insertAspectRatioMenuItems.cpp:20
+msgid "&DVB (2.11:1)"
+msgstr ""
+
+#: ../src/app/main.cpp:14
+msgid "A video player that has a usability focus"
+msgstr ""
+
+#: ../src/app/main.cpp:15
+msgid "Copyright 2005, Max Howell"
+msgstr ""
+
+#: ../src/app/main.cpp:19
+msgid "Play 'URL'"
+msgstr ""
+
+#: ../src/app/main.cpp:28
+msgid "Handbook"
+msgstr ""
+
+#: ../src/app/main.cpp:29
+msgid "Great reference code"
+msgstr ""
+
+#: ../src/app/main.cpp:30
+msgid "The current Codeine icon"
+msgstr ""
+
+#: ../src/app/main.cpp:31
+msgid "The video for \"Call on Me\" encouraged plenty of debugging! ;)"
+msgstr ""
+
+#: ../src/app/playlistFile.cpp:32
+msgid "The file is not a playlist"
+msgstr ""
+
+#: ../src/app/playlistFile.cpp:39
+msgid "Codeine could not download the remote playlist: %1"
+msgstr ""
+
+#: ../src/app/playlistFile.cpp:54
+msgid ""
+"<qt>The playlist, <i>'%1'</i>, could not be interpreted. Perhaps it is empty?"
+msgstr ""
+
+#: ../src/app/playlistFile.cpp:58
+msgid "Codeine could not open the file: %1"
+msgstr ""
+
+#: ../src/app/stateChange.cpp:83
+msgid "&Pause"
+msgstr ""
+
+#: ../src/app/stateChange.cpp:83
+msgid "&Play"
+msgstr ""
+
+#: ../src/app/stateChange.cpp:147
+msgid "No media loaded"
+msgstr ""
+
+#: ../src/app/stateChange.cpp:150
+msgid "Paused"
+msgstr ""
+
+#: ../src/app/actions.cpp:13 ../src/part/part.cpp:38
+msgid "Play"
+msgstr ""
+
+#: ../src/app/xineEngine.cpp:127 ../src/part/xineEngine.cpp:50
+msgid "xine was unable to initialize any video-drivers."
+msgstr ""
+
+#: ../src/app/xineEngine.cpp:129 ../src/part/xineEngine.cpp:48
+msgid "xine was unable to initialize any audio-drivers."
+msgstr ""
+
+#: ../src/app/xineEngine.cpp:231
+msgid "Loading media: %1"
+msgstr ""
+
+#: ../src/app/xineEngine.cpp:333
+msgid "Recording to: %1"
+msgstr ""
+
+#: ../src/app/xineEngine.cpp:364
+msgid "Playback paused"
+msgstr ""
+
+#: ../src/app/xineEngine.cpp:369
+msgid "Playback resumed"
+msgstr ""
+
+#: ../src/app/xineEngine.cpp:382
+msgid "There is no input plugin that can read: %1."
+msgstr ""
+
+#: ../src/app/xineEngine.cpp:385
+msgid "There is no demux plugin available for %1."
+msgstr ""
+
+#: ../src/app/xineEngine.cpp:388
+msgid "Demuxing failed for %1."
+msgstr ""
+
+#: ../src/app/xineEngine.cpp:393
+msgid "Internal error while attempting to play %1."
+msgstr ""
+
+#: ../src/app/xineEngine.cpp:433
+msgid "xine cannot currently seek in flac media"
+msgstr ""
+
+#: ../src/app/xineEngine.cpp:585 ../src/app/xineEngine.cpp:593
+msgid "Channel %1"
+msgstr ""
+
+#: ../src/app/xineEngine.cpp:692 ../src/part/xineEngine.cpp:282
+msgid "The source is encrypted and can not be decrypted."
+msgstr ""
+
+#: ../src/app/xineEngine.cpp:694 ../src/part/xineEngine.cpp:284
+msgid "The host is unknown for the URL: <i>%1</i>"
+msgstr ""
+
+#: ../src/app/xineEngine.cpp:696 ../src/part/xineEngine.cpp:286
+msgid "The device name you specified seems invalid."
+msgstr ""
+
+#: ../src/app/xineEngine.cpp:698 ../src/part/xineEngine.cpp:288
+msgid "The network appears unreachable."
+msgstr ""
+
+#: ../src/app/xineEngine.cpp:700 ../src/part/xineEngine.cpp:290
+msgid "Audio output unavailable; the device is busy."
+msgstr ""
+
+#: ../src/app/xineEngine.cpp:702 ../src/part/xineEngine.cpp:292
+msgid "The connection was refused for the URL: <i>%1</i>"
+msgstr ""
+
+#: ../src/app/xineEngine.cpp:704 ../src/part/xineEngine.cpp:294
+msgid "xine could not find the URL: <i>%1</i>"
+msgstr ""
+
+#: ../src/app/xineEngine.cpp:706 ../src/part/xineEngine.cpp:296
+msgid "Access was denied for the URL: <i>%1</i>"
+msgstr ""
+
+#: ../src/app/xineEngine.cpp:708 ../src/part/xineEngine.cpp:298
+msgid "The source cannot be read for the URL: <i>%1</i>"
+msgstr ""
+
+#: ../src/app/xineEngine.cpp:710 ../src/part/xineEngine.cpp:300
+msgid "A problem occurred while loading a library or decoder."
+msgstr ""
+
+#: ../src/app/xineEngine.cpp:737 ../src/part/xineEngine.cpp:327
+msgid "Sorry, no additional information is available."
+msgstr ""
+
+#: ../src/app/captureFrame.cpp:82
+msgid "Capture - %1"
+msgstr ""
+
+#: ../src/app/captureFrame.cpp:98
+msgid ""
+"*.png|PNG Format\n"
+"*.jpeg|JPEG Format"
+msgstr ""
+
+#: ../src/app/captureFrame.cpp:100
+msgid "Save Frame"
+msgstr ""
+
+#: ../src/app/captureFrame.cpp:111
+msgid "%1 saved successfully"
+msgstr ""
+
+#: ../src/app/captureFrame.cpp:113
+msgid "Sorry, could not save %1"
+msgstr ""
+
+#: ../src/app/fullScreenAction.cpp:31
+msgid "Exit F&ull Screen Mode"
+msgstr ""
+
+#: ../src/app/fullScreenAction.cpp:37
+msgid "F&ull Screen Mode"
+msgstr ""
+
+#: ../src/app/mainWindow.cpp:94
+msgid "&Subtitles"
+msgstr ""
+
+#: ../src/app/mainWindow.cpp:95
+msgid "A&udio Channels"
+msgstr ""
+
+#: ../src/app/mainWindow.cpp:96
+msgid "Aspect &Ratio"
+msgstr ""
+
+#: ../src/app/mainWindow.cpp:106
+msgid "<qt>"
+msgstr ""
+
+#: ../src/app/mainWindow.cpp:106
+msgid " could not load its interface, this probably means that "
+msgstr ""
+
+#: ../src/app/mainWindow.cpp:106
+msgid ""
+" 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>"
+msgstr ""
+
+#: ../src/app/mainWindow.cpp:139
+msgid "<qt>xine could not be successfully initialised. "
+msgstr ""
+
+#: ../src/app/mainWindow.cpp:139
+msgid ""
+" 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."
+msgstr ""
+
+#: ../src/app/mainWindow.cpp:208
+msgid "Play &Media..."
+msgstr ""
+
+#: ../src/app/mainWindow.cpp:214
+msgid "Record"
+msgstr ""
+
+#: ../src/app/mainWindow.cpp:216
+msgid "Reset Video Scale"
+msgstr ""
+
+#: ../src/app/mainWindow.cpp:217 ../src/app/mainWindow.cpp:423
+msgid "Media Information"
+msgstr ""
+
+#: ../src/app/mainWindow.cpp:218
+msgid "Menu Toggle"
+msgstr ""
+
+#: ../src/app/mainWindow.cpp:219
+msgid "&Capture Frame"
+msgstr ""
+
+#: ../src/app/mainWindow.cpp:221
+msgid "Video Settings..."
+msgstr ""
+
+#: ../src/app/mainWindow.cpp:222
+msgid "Configure xine..."
+msgstr ""
+
+#: ../src/app/mainWindow.cpp:224
+msgid "Position Slider"
+msgstr ""
+
+#: ../src/app/mainWindow.cpp:317
+msgid "Codeine was asked to open an empty URL; it cannot."
+msgstr ""
+
+#: ../src/app/mainWindow.cpp:366
+msgid "Supported Media Formats"
+msgstr ""
+
+#: ../src/app/mainWindow.cpp:366
+msgid "All Files"
+msgstr ""
+
+#: ../src/app/mainWindow.cpp:367
+msgid "Select A File To Play"
+msgstr ""
+
+#: ../src/app/mainWindow.cpp:438
+msgid "&Determine Automatically"
+msgstr ""
+
+#: ../src/app/mainWindow.cpp:450
+msgid "&Off"
+msgstr ""
+
+#: ../src/app/mainWindow.cpp:492
+msgid "Sorry, no media was found in the drop"
+msgstr ""
+
+#: ../src/app/theStream.cpp:107
+msgid "Metadata"
+msgstr ""
+
+#: ../src/app/theStream.cpp:109
+msgid "Title"
+msgstr ""
+
+#: ../src/app/theStream.cpp:110
+msgid "Comment"
+msgstr ""
+
+#: ../src/app/theStream.cpp:111
+msgid "Artist"
+msgstr ""
+
+#: ../src/app/theStream.cpp:112
+msgid "Genre"
+msgstr ""
+
+#: ../src/app/theStream.cpp:113
+msgid "Album"
+msgstr ""
+
+#: ../src/app/theStream.cpp:114
+msgid "Year"
+msgstr ""
+
+#: ../src/app/theStream.cpp:116
+msgid "Audio Properties"
+msgstr ""
+
+#: ../src/app/theStream.cpp:118
+msgid "Bitrate"
+msgstr ""
+
+#: ../src/app/theStream.cpp:118
+msgid "%1 bps"
+msgstr ""
+
+#: ../src/app/theStream.cpp:119
+msgid "Sample-rate"
+msgstr ""
+
+#: ../src/app/theStream.cpp:119
+msgid "%1 Hz"
+msgstr ""
+
+#: ../src/app/theStream.cpp:121
+msgid "Technical Information"
+msgstr ""
+
+#: ../src/app/theStream.cpp:123
+msgid "Video Codec"
+msgstr ""
+
+#: ../src/app/theStream.cpp:124
+msgid "Audio Codec"
+msgstr ""
+
+#: ../src/app/theStream.cpp:125
+msgid "System Layer"
+msgstr ""
+
+#: ../src/app/theStream.cpp:126
+msgid "Input Plugin"
+msgstr ""
+
+#: ../src/app/theStream.cpp:127
+msgid "CDINDEX_DISCID"
+msgstr ""
+
+#: ../src/app/videoWindow.cpp:140
+msgid "Pause"
+msgstr ""
+
+#: ../src/part/xineEngine.cpp:159
+msgid "The Codeine video player could not find an input plugin for '%1'."
+msgstr ""
+
+#: ../src/part/xineEngine.cpp:162
+msgid "The Codeine video player could not find a demux plugin for '%1'."
+msgstr ""
+
+#: ../src/part/xineEngine.cpp:165
+msgid ""
+"The Codeine video player failed to demux '%1'; please check your xine "
+"installation."
+msgstr ""
+
+#: ../src/part/xineEngine.cpp:170
+msgid ""
+"The Codeine video player reports an internal error; please check your xine "
+"installation."
+msgstr ""
+
+#: ../src/_translatorinfo.cpp:1
+msgid ""
+"_: NAME OF TRANSLATORS\n"
+"Your names"
+msgstr ""
+
+#: ../src/_translatorinfo.cpp:3
+msgid ""
+"_: EMAIL OF TRANSLATORS\n"
+"Your emails"
+msgstr ""
diff --git a/po/messages.sh b/po/messages.sh
new file mode 100755
index 0000000..f3103f9
--- /dev/null
+++ b/po/messages.sh
@@ -0,0 +1,60 @@
+#!/bin/sh
+
+# Inspired by Makefile.common from coolo
+# this script is used to update the .po files
+
+# To update the translations, you will need a specific gettext
+# patched for kde and a lot of patience, tenacity, luck, time ..
+
+
+# I guess one should only update the .po files when all .cpp files
+# are generated (after a make or scons)
+
+# If you have a better way to do this, do not keep that info
+# for yourself and help me to improve this script, thanks
+# (tnagyemail-mail tat yahoo d0tt fr)
+
+SRCDIR=../src # srcdir is the directory containing the source code
+TIPSDIR=$SRCDIR # tipsdir is the directory containing the tips
+
+KDEDIR=`kde-config --prefix`
+EXTRACTRC=extractrc
+KDEPOT=`kde-config --prefix`/include/kde.pot
+XGETTEXT="xgettext -C -ki18n -ktr2i18n -kI18N_NOOP -ktranslate -kaliasLocale -x $KDEPOT "
+
+## check that kde.pot is available
+if ! test -e $KDEPOT; then
+ echo "$KDEPOT does not exist, there is something wrong with your installation!"
+ XGETTEXT="xgettext -C -ki18n -ktr2i18n -kI18N_NOOP -ktranslate -kaliasLocale "
+fi
+
+> rc.cpp
+
+## extract the strings
+echo "extracting the strings"
+
+# process the .ui and .rc files
+$EXTRACTRC `find $SRCDIR -iname *.rc` >> rc.cpp
+$EXTRACTRC `find $SRCDIR -iname *.ui` >> rc.cpp
+echo -e 'i18n("_: NAME OF TRANSLATORS\\n"\n"Your names")\ni18n("_: EMAIL OF TRANSLATORS\\n"\n"Your emails")' > $SRCDIR/_translatorinfo.cpp
+
+# process the tips - $SRCDIR is supposed to be where the tips are living
+pushd $TIPSDIR; preparetips >tips.cpp; popd
+
+$XGETTEXT `find $SRCDIR -name "*.cpp"` -o codeine.pot
+
+# remove the intermediate files
+rm -f $TIPSDIR/tips.cpp
+rm -f rc.cpp
+rm -f $SRCDIR/_translatorinfo.cpp
+
+## now merge the .po files ..
+echo "merging the .po files"
+
+for i in `ls *.po`; do
+ msgmerge $i kdissert.pot -o $i || exit 1
+done
+
+## finished
+echo "Done"
+
diff --git a/scons/codeine.py b/scons/codeine.py
new file mode 100644
index 0000000..742cc78
--- /dev/null
+++ b/scons/codeine.py
@@ -0,0 +1,101 @@
+## Max Howell, 2005
+
+BOLD ="\033[1m"
+RED ="\033[91m"
+GREEN ="\033[92m"
+YELLOW ="\033[93m"
+CYAN ="\033[96m"
+NORMAL ="\033[0m"
+
+import os
+
+def exists( env ):
+ return true
+
+def generate( env ):
+
+ if 'configure' in env['TARGS']:
+ xine_lib_test_source_file = """
+ #include <qstring.h>
+ #include <xine.h>
+
+ int main( int argc, char **argv )
+ {
+ if( XINE_MAJOR_VERSION < 1 )
+ return 1;
+
+ const QString version( XINE_VERSION );
+
+ // eg. VERSION 1.0
+ if( version[1] == '.' )
+ return 0;
+
+ if( version == "1-cvs" )
+ return 0;
+
+ if( version.startsWith( "1-rc" ) && QString(version[4]).toInt() > 3 )
+ return 0;
+
+ return 2; //too old
+ }"""
+
+ def CheckKdeLibs( context ):
+ # ideally should be able to tell bksys what version we need
+ context.Message( 'Checking for KDElibs 3.3...' )
+ kde_version = os.popen("kde-config --version|grep KDE").read().strip().split()[1]
+ result = int( kde_version[0] ) == 3 and int( kde_version[2] ) >= 3
+ context.Result( result )
+ return result
+
+ def CheckXineLib( context ):
+ context.Message('Checking for xine-lib 1.0...')
+ result = context.TryLink(xine_lib_test_source_file, '.cpp')
+ context.Result(result)
+ return result
+
+
+ # prolly best to use a fresh env
+ # this seems to import the user's CXXFLAGS, etc., which may break
+ confenv = env.Copy()
+ configure = confenv.Configure(custom_tests = {'CheckXineLib' : CheckXineLib, 'CheckKdeLibs' : CheckKdeLibs}, log_file='configure.log')
+ confenv.AppendUnique(LIBS = 'qt-mt')
+ confenv.AppendUnique(LINKFLAGS = '-L/usr/X11R6/lib')
+
+ if not configure.CheckKdeLibs():
+ print # 1 2 3 4 5 6 7 8'
+ print 'Configure could not detect KDElibs 3.3, which is required for Codeine to '
+ print 'compile.'
+ print
+ confenv.Exit( 1 )
+
+ if not configure.CheckLibWithHeader( 'xine', 'xine.h', 'c++' ):
+ print # 1 2 3 4 5 6 7 8'
+ print 'Configure could not find either the xine library or header on your system. You '
+ print 'should ammend the relevant paths. If you know which ones please email me so I '
+ print 'can update this message!'
+ print
+ confenv.Exit( 2 )
+
+ if not configure.CheckXineLib():
+ print # 1 2 3 4 5 6 7 8'
+ print 'Your xine-lib is either too old, or can not be linked against. Sorry for not '
+ print 'being more specific..'
+ print
+ confenv.Exit( 3 )
+
+ if not configure.CheckLibWithHeader( 'Xtst', 'X11/extensions/XTest.h', 'c' ):
+ print # 1 2 3 4 5 6 7 8'
+ print 'libxtst was not found, this means the screensaver cannot be disabled during '
+ print 'playback. YOU CAN STILL BUILD CODEINE! :)'
+ print
+
+ file = open ( 'src/configure.h', 'w' )
+ file.write( "#define NO_XTEST_EXTENSION\n" )
+ file.close()
+ else:
+ # FIXME - thus only one thing can be in configure.h - lol
+ file = open ( 'src/configure.h', 'w' )
+ file.write( "" )
+ file.close()
+
+ env = configure.Finish()
diff --git a/scons/generic.py b/scons/generic.py
new file mode 100644
index 0000000..3249df7
--- /dev/null
+++ b/scons/generic.py
@@ -0,0 +1,95 @@
+## Thomas Nagy, 2005
+
+"""
+Detect and store the most common options
+* kdecxxflags : debug=1 (-g) or debug=full (-g3, slower)
+ else use the user CXXFLAGS if any, - or -O2 by default
+* prefix : the installation path
+* extraincludes : a list of paths separated by ':'
+ie: scons configure debug=full prefix=/usr/local extraincludes=/tmp/include:/usr/local
+"""
+
+BOLD ="\033[1m"
+RED ="\033[91m"
+GREEN ="\033[92m"
+YELLOW ="\033[93m"
+CYAN ="\033[96m"
+NORMAL ="\033[0m"
+
+import os
+
+def exists(env):
+ return true
+
+def generate(env):
+ env.Help("""
+"""+BOLD+
+"""*** Generic options ***
+-----------------------"""+NORMAL+"""
+"""+BOLD+"""* debug """+NORMAL+""": debug=1 (-g) or debug=full (-g3, slower) else use environment CXXFLAGS, or -O2 by default
+"""+BOLD+"""* prefix """+NORMAL+""": the installation path
+"""+BOLD+"""* extraincludes """+NORMAL+""": a list of paths separated by ':'
+ie: """+BOLD+"""scons configure debug=full prefix=/usr/local extraincludes=/tmp/include:/usr/local
+"""+NORMAL)
+
+ # load the options
+ from SCons.Options import Options, PathOption
+ opts = Options('generic.cache.py')
+ opts.AddOptions(
+ ( 'KDECXXFLAGS', 'debug level for the project : full or just anything' ),
+ ( 'PREFIX', 'prefix for installation' ),
+ ( 'EXTRAINCLUDES', 'extra include paths for the project' ),
+ )
+ opts.Update(env)
+
+ # use this to avoid an error message 'how to make target configure ?'
+ env.Alias('configure', None)
+
+ # configure the environment if needed
+ if 'configure' in env['TARGS'] or not env.has_key('KDECXXFLAGS'):
+ # need debugging ?
+ if env.has_key('KDECXXFLAGS'):
+ env.__delitem__('KDECXXFLAGS')
+ if env['ARGS'].get('debug', None):
+ debuglevel = env['ARGS'].get('debug', None)
+ print CYAN+'** Enabling debug for the project **' + NORMAL
+ if (debuglevel == "full"):
+ env['KDECXXFLAGS'] = ['-DDEBUG', '-ggdb', '-pipe', '-Wall']
+ else:
+ env['KDECXXFLAGS'] = ['-DDEBUG', '-g']
+ else:
+ if os.environ.has_key('CXXFLAGS'):
+ # user-defined flags (gentooers will be delighted)
+ import SCons.Util
+ env['KDECXXFLAGS'] = SCons.Util.CLVar( os.environ['CXXFLAGS'] )
+ env.Append( KDECXXFLAGS = ['-DNDEBUG', '-DNO_DEBUG'] )
+ else:
+ env.Append(KDECXXFLAGS = ['-O2', '-DNDEBUG', '-DNO_DEBUG'])
+
+ # user-specified prefix
+ if env['ARGS'].get('prefix', None):
+ env['PREFIX'] = env['ARGS'].get('prefix', None)
+ print CYAN+'** set the installation prefix for the project : ' + env['PREFIX'] +' **'+ NORMAL
+ elif env.has_key('PREFIX'):
+ env.__delitem__('PREFIX')
+
+ # user-specified include paths
+ env['EXTRAINCLUDES'] = env['ARGS'].get('extraincludes', None)
+ if env['ARGS'].get('extraincludes', None):
+ print CYAN+'** set extra include paths for the project : ' + env['EXTRAINCLUDES'] +' **'+ NORMAL
+ elif env.has_key('EXTRAINCLUDES'):
+ env.__delitem__('EXTRAINCLUDES')
+
+ # and finally save the options in a cache
+ opts.Save('generic.cache.py', env)
+
+ if env.has_key('KDECXXFLAGS'):
+ # load the flags
+ env.AppendUnique( CPPFLAGS = env['KDECXXFLAGS'] )
+
+ if env.has_key('EXTRAINCLUDES'):
+ incpaths = []
+ for dir in str(env['EXTRAINCLUDES']).split(':'):
+ incpaths.append( dir )
+ env.Append(CPPPATH = incpaths)
+
diff --git a/scons/kde.py b/scons/kde.py
new file mode 100644
index 0000000..8fa492b
--- /dev/null
+++ b/scons/kde.py
@@ -0,0 +1,771 @@
+# Copyright (c) 2001, 2002, 2003, 2004 The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+# Shamelessly stolen from qt.py and (heavily) modified into kde.py :)
+# Thomas Nagy, 2004, 2005 <tnagy2^8@yahoo.fr>
+
+"""
+Here follow the basic rules for building kde programs
+The detection is done in detect_kde when needed
+We wan to use the cached variables as much as possible
+
+The variables used when configuring are :
+* prefix : base install path, eg: /usr/local
+* execprefix : install path for binaries, eg: /usr/bin
+* datadir : install path for the data, eg: /usr/local/share
+* libdir : install path for the libs, eg: /usr/lib
+
+* libsuffix : for those who need /usr/lib64 and the like ..
+
+* kdeincludes: path to the kde includes (/usr/include/kde on debian, ...)
+* qtincludes : same punishment, for qt includes (/usr/include/qt on debian, ...)
+
+* kdelibs : path to the kde libs, for linking the programs
+* qtlibs : same punishment, for qt libraries
+
+eg: scons configure libdir=/usr/local/lib qtincludes=/usr/include/qt
+"""
+
+BOLD ="\033[1m"
+RED ="\033[91m"
+GREEN ="\033[92m"
+YELLOW ="\033[93m"
+CYAN ="\033[96m"
+NORMAL ="\033[0m"
+
+def exists(env):
+ return True
+
+def detect_kde(env):
+ """ Detect the qt and kde environment using kde-config mostly """
+ import os, sys, re
+
+ prefix = env['ARGS'].get('prefix', None)
+ execprefix = env['ARGS'].get('execprefix', None)
+ datadir = env['ARGS'].get('datadir', None)
+ libdir = env['ARGS'].get('libdir', None)
+ libsuffix = env['ARGS'].get('libsuffix', '')
+ kdeincludes = env['ARGS'].get('kdeincludes', None)
+ kdelibs = env['ARGS'].get('kdelibs', None)
+ qtincludes = env['ARGS'].get('qtincludes', None)
+ qtlibs = env['ARGS'].get('qtlibs', None)
+
+ if libdir:
+ libdir = libdir+libsuffix
+
+ ## Detect the kde libraries
+ print "Checking for kde-config : ",
+ kde_config = os.popen("which kde-config 2>/dev/null").read().strip()
+ if len(kde_config):
+ print GREEN + "kde-config was found" + NORMAL
+ else:
+ print RED + "kde-config was NOT found in your PATH"+ NORMAL
+ print "Make sure kde is installed properly"
+ print "(missing package kdebase-devel?)"
+ # TODO : prompt the user for the path of kde-config ?
+ sys.exit(1)
+ env['KDEDIR'] = os.popen('kde-config -prefix').read().strip()
+
+ print "Checking for kde version : ",
+ kde_version = os.popen("kde-config --version|grep KDE").read().strip().split()[1]
+ if int(kde_version[0]) != 3 or int(kde_version[2]) < 2:
+ print RED + kde_version
+ print RED + "Your kde version can be too old" + NORMAL
+ print RED + "Please make sure kde is at least 3.2" + NORMAL
+ else:
+ print GREEN + kde_version + NORMAL
+
+ ## Detect the qt library
+ print "Checking for the qt library : ",
+ qtdir = os.getenv("QTDIR")
+ if qtdir:
+ print GREEN + "qt is in " + qtdir + NORMAL
+ else:
+ m = re.search('(.*)/lib/libqt.*', os.popen('ldd `kde-config --expandvars --install lib`' + '/libkdeui.so.4 | grep libqt').read().strip().split()[2])
+ if m:
+ qtdir = m.group(1)
+ print YELLOW + "qt was found as " + m.group(1) + NORMAL
+ else:
+ print RED + "qt was not found" + NORMAL
+ print RED + "Please set QTDIR first (/usr/lib/qt3?)" + NORMAL
+ sys.exit(1)
+ env['QTDIR'] = qtdir.strip()
+
+ ## Find the necessary programs uic and moc
+ print "Checking for uic : ",
+ uic = qtdir + "/bin/uic"
+ if os.path.isfile(uic):
+ print GREEN + "uic was found as " + uic + NORMAL
+ else:
+ uic = os.popen("which uic 2>/dev/null").read().strip()
+ if len(uic):
+ print YELLOW + "uic was found as " + uic + NORMAL
+ else:
+ uic = os.popen("which uic 2>/dev/null").read().strip()
+ if len(uic):
+ print YELLOW + "uic was found as " + uic + NORMAL
+ else:
+ print RED + "uic was not found - set QTDIR put it in your PATH ?" + NORMAL
+ sys.exit(1)
+ env['QT_UIC'] = uic
+
+ print "Checking for moc : ",
+ moc = qtdir + "/bin/moc"
+ if os.path.isfile(moc):
+ print GREEN + "moc was found as " + moc + NORMAL
+ else:
+ moc = os.popen("which moc 2>/dev/null").read().strip()
+ if len(moc):
+ print YELLOW + "moc was found as " + moc + NORMAL
+ elif os.path.isfile("/usr/share/qt3/bin/moc"):
+ moc = "/usr/share/qt3/bin/moc"
+ print YELLOW + "moc was found as " + moc + NORMAL
+ else:
+ print RED + "moc was not found - set QTDIR or put it in your PATH ?" + NORMAL
+ sys.exit(1)
+ env['QT_MOC'] = moc
+
+ ## check for the qt and kde includes
+ print "Checking for the qt includes : ",
+ if qtincludes and os.path.isfile(qtincludes + "/qlayout.h"):
+ # The user told where to look for and it looks valid
+ print GREEN + "ok " + qtincludes + NORMAL
+ else:
+ if os.path.isfile(qtdir + "/include/qlayout.h"):
+ # Automatic detection
+ print GREEN + "ok " + qtdir + "/include/ " + NORMAL
+ qtincludes = qtdir + "/include/"
+ elif os.path.isfile("/usr/include/qt3/qlayout.h"):
+ # Debian probably
+ print YELLOW + "the qt headers were found in /usr/include/qt3/ " + NORMAL
+ qtincludes = "/usr/include/qt3"
+ else:
+ print RED + "the qt headers were not found" + NORMAL
+ sys.exit(1)
+
+ print "Checking for the kde includes : ",
+ kdeprefix = os.popen("kde-config --prefix").read().strip()
+ if not kdeincludes:
+ kdeincludes = kdeprefix+"/include/"
+ if os.path.isfile(kdeincludes + "/klineedit.h"):
+ print GREEN + "ok " + kdeincludes + NORMAL
+ else:
+ if os.path.isfile(kdeprefix+"/include/kde/klineedit.h"):
+ # Debian, Fedora probably
+ print YELLOW + "the kde headers were found in " + kdeprefix + "/include/kde/" + NORMAL
+ kdeincludes = kdeprefix + "/include/kde/"
+ else:
+ print RED + "The kde includes were NOT found" + NORMAL
+ sys.exit(1)
+
+ if prefix:
+ ## use the user-specified prefix
+ if not execprefix:
+ execprefix = prefix
+ if not datadir:
+ datadir = prefix + "/share"
+ if not libdir:
+ libdir = execprefix + "/lib"+libsuffix
+
+ subst_vars = lambda x: x.replace('${exec_prefix}', execprefix).replace('${datadir}',
+ datadir).replace('${libdir}', libdir)
+ debian_fix = lambda x: x.replace('/usr/share', '${datadir}')
+ env['KDEBIN'] = subst_vars(os.popen('kde-config --install exe').read().strip())
+ env['KDEAPPS'] = subst_vars(os.popen('kde-config --install apps').read().strip())
+ env['KDEDATA'] = subst_vars(os.popen('kde-config --install data').read().strip())
+ env['KDEMODULE']= subst_vars(os.popen('kde-config --install module').read().strip())
+ env['KDELOCALE']= subst_vars(os.popen('kde-config --install locale').read().strip())
+ env['KDEDOC'] = subst_vars( debian_fix(os.popen('kde-config --install html').read().strip()) )
+ env['KDEKCFG'] = subst_vars(os.popen('kde-config --install kcfg').read().strip())
+ env['KDEXDG'] = subst_vars(os.popen('kde-config --install xdgdata-apps').read().strip())
+ env['KDEMENU'] = subst_vars(os.popen('kde-config --install apps').read().strip())
+ env['KDEMIME'] = subst_vars(os.popen('kde-config --install mime').read().strip())
+ env['KDEICONS'] = subst_vars(os.popen('kde-config --install icon').read().strip())
+ env['KDESERV'] = subst_vars(os.popen('kde-config --install services').read().strip())
+ else:
+ # the user has given no prefix, install as a normal kde app
+ env['PREFIX'] = os.popen('kde-config --prefix').read().strip()
+
+ env['KDEBIN'] = os.popen('kde-config --expandvars --install exe').read().strip()
+ env['KDEAPPS'] = os.popen('kde-config --expandvars --install apps').read().strip()
+ env['KDEDATA'] = os.popen('kde-config --expandvars --install data').read().strip()
+ env['KDEMODULE']= os.popen('kde-config --expandvars --install module').read().strip()
+ env['KDELOCALE']= os.popen('kde-config --expandvars --install locale').read().strip()
+ env['KDEDOC'] = os.popen('kde-config --expandvars --install html').read().strip()
+ env['KDEKCFG'] = os.popen('kde-config --expandvars --install kcfg').read().strip()
+ env['KDEXDG'] = os.popen('kde-config --expandvars --install xdgdata-apps').read().strip()
+ env['KDEMENU'] = os.popen('kde-config --expandvars --install apps').read().strip()
+ env['KDEMIME'] = os.popen('kde-config --expandvars --install mime').read().strip()
+ env['KDEICONS'] = os.popen('kde-config --expandvars --install icon').read().strip()
+ env['KDESERV'] = os.popen('kde-config --expandvars --install services').read().strip()
+
+ env['QTPLUGINS']=os.popen('kde-config --expandvars --install qtplugins').read().strip()
+
+ ## kde libs and includes
+ env['KDEINCLUDEPATH']= kdeincludes
+ if not kdelibs:
+ kdelibs = os.popen('kde-config --expandvars --install lib').read().strip()
+ env['KDELIBPATH']= kdelibs
+
+ ## qt libs and includes
+ env['QTINCLUDEPATH']= qtincludes
+ if not qtlibs:
+ qtlibs = qtdir+ "/lib"
+ env['QTLIBPATH']= qtlibs
+
+
+def generate(env):
+ """"Set up the qt and kde environment and builders - the moc part is difficult to understand """
+
+ env.Help("""
+"""+BOLD+
+"""*** KDE options ***
+-------------------"""
++NORMAL+"""
+"""+BOLD+"""* prefix """+NORMAL+""": base install path, ie: /usr/local
+"""+BOLD+"""* execprefix """+NORMAL+""": install path for binaries, ie: /usr/bin
+"""+BOLD+"""* datadir """+NORMAL+""": install path for the data, ie: /usr/local/share
+"""+BOLD+"""* libdir """+NORMAL+""": install path for the libs, ie: /usr/lib
+"""+BOLD+"""* libsuffix """+NORMAL+""": suffix of libraries on amd64, ie: 64, 32
+"""+BOLD+"""* kdeincludes"""+NORMAL+""": path to the kde includes (/usr/include/kde on debian, ...)
+"""+BOLD+"""* qtincludes """+NORMAL+""": same punishment, for qt includes (/usr/include/qt on debian, ...)
+"""+BOLD+"""* kdelibs """+NORMAL+""": path to the kde libs, for linking the programs
+"""+BOLD+"""* qtlibs """+NORMAL+""": same punishment, for qt libraries
+ie: """+BOLD+"""scons configure libdir=/usr/local/lib qtincludes=/usr/include/qt
+"""+NORMAL)
+
+ import os.path
+ import re
+
+ import SCons.Defaults
+ import SCons.Tool
+ import SCons.Util
+
+ ui_extensions = [".ui", ".Ui", ".UI"]
+ header_extensions = [".h", ".hxx", ".hpp", ".hh", ".H", ".HH"]
+ source_extensions = [".cpp", ".cxx", ".cc", ".CPP", ".CXX", ".CC"]
+
+ def find_file(filename, paths, node_factory):
+ retval = None
+ for dir in paths:
+ node = node_factory(filename, dir)
+ if node.rexists():
+ return node
+ return None
+
+ class _Metasources:
+ """ Callable class, which works as an emitter for Programs, SharedLibraries
+ and StaticLibraries."""
+
+ def __init__(self, objBuilderName):
+ self.objBuilderName = objBuilderName
+
+ def __call__(self, target, source, env):
+ """ Smart autoscan function. Gets the list of objects for the Program
+ or Lib. Adds objects and builders for the special qt files. """
+ try:
+ if int(env.subst('$QT_AUTOSCAN')) == 0:
+ return target, source
+ except ValueError:
+ pass
+
+ try:
+ qtdebug = int(env.subst('$QT_DEBUG'))
+ except ValueError:
+ qtdebug = 0
+
+ # some shortcuts used in the scanner
+ FS = SCons.Node.FS.default_fs
+ splitext = SCons.Util.splitext
+ objBuilder = getattr(env, self.objBuilderName)
+
+ # some regular expressions:
+ # Q_OBJECT detection
+ q_object_search = re.compile(r'[^A-Za-z0-9]Q_OBJECT[^A-Za-z0-9]')
+
+ # cxx and c comment 'eater'
+ #comment = re.compile(r'(//.*)|(/\*(([^*])|(\*[^/]))*\*/)')
+ # CW: something must be wrong with the regexp. See also bug #998222
+ # CURRENTLY THERE IS NO TEST CASE FOR THAT
+
+ # The following is kind of hacky to get builders working properly (FIXME)
+ objBuilderEnv = objBuilder.env
+ objBuilder.env = env
+ mocBuilderEnv = env.Moc.env
+ env.Moc.env = env
+
+ # make a deep copy for the result; MocH objects will be appended
+ out_sources = source[:]
+
+ for obj in source:
+ if not obj.has_builder():
+ # binary obj file provided
+ if qtdebug:
+ print "scons: qt: '%s' seems to be a binary. Discarded." % str(obj)
+ continue
+ cpp = obj.sources[0]
+ if not splitext(str(cpp))[1] in source_extensions:
+ if qtdebug:
+ print "scons: qt: '%s' is no cxx file. Discarded." % str(cpp)
+ # c or fortran source
+ continue
+ #cpp_contents = comment.sub('', cpp.get_contents())
+ cpp_contents = cpp.get_contents()
+
+ h = None
+ ui = None
+
+ for ui_ext in ui_extensions:
+ # try to find the ui file in the corresponding source directory
+ uiname = splitext(cpp.name)[0] + ui_ext
+ ui = find_file(uiname, (cpp.get_dir(),), FS.File)
+ if ui:
+ if qtdebug:
+ print "scons: qt: found .ui file of header" #% (str(h), str(cpp))
+ #h_contents = comment.sub('', h.get_contents())
+ break
+
+ # if we have a .ui file, do not continue, it is automatically handled by Uic
+ if ui:
+ continue
+
+ for h_ext in header_extensions:
+ # try to find the header file in the corresponding source
+ # directory
+ hname = splitext(cpp.name)[0] + h_ext
+ h = find_file(hname, (cpp.get_dir(),), FS.File)
+ if h:
+ if qtdebug:
+ print "scons: qt: Scanning '%s' (header of '%s')" % (str(h), str(cpp))
+ #h_contents = comment.sub('', h.get_contents())
+ h_contents = h.get_contents()
+ break
+
+ if not h and qtdebug:
+ print "scons: qt: no header for '%s'." % (str(cpp))
+ if h and q_object_search.search(h_contents):
+ # h file with the Q_OBJECT macro found -> add .moc or _moc.cpp file
+ moc_cpp = None
+
+ if env.has_key('NOMOCSCAN'):
+ moc_cpp = env.Moc(h)
+ else:
+ reg = '\n\s*#include\s+"'+splitext(cpp.name)[0]+'.moc"'
+ meta_object_search = re.compile(reg)
+ if meta_object_search.search(cpp_contents):
+ moc_cpp = env.Moc(h)
+ else:
+ moc_cpp = env.Moccpp(h)
+ moc_o = objBuilder(moc_cpp)
+ out_sources.append(moc_o)
+ if qtdebug:
+ print "scons: qt: found Q_OBJECT macro in '%s', moc'ing to '%s'" % (str(h), str(moc_cpp[0]))
+
+ if cpp and q_object_search.search(cpp_contents):
+ print "error, bksys cannot handle cpp files with Q_OBJECT classes"
+ print "if you are sure this is a feature worth the effort, "
+ print "report this to the authors tnagyemail-mail yahoo.fr"
+
+ # restore the original env attributes (FIXME)
+ objBuilder.env = objBuilderEnv
+ env.Moc.env = mocBuilderEnv
+
+ return (target, out_sources)
+
+ MetasourcesShared = _Metasources('SharedObject')
+ MetasourcesStatic = _Metasources('StaticObject')
+
+ CLVar = SCons.Util.CLVar
+ splitext = SCons.Util.splitext
+ Builder = SCons.Builder.Builder
+
+ # Detect the environment - replaces ./configure implicitely
+ # and store the options into a cache
+ from SCons.Options import Options
+ opts = Options('kde.cache.py')
+ opts.AddOptions(
+ ( 'QTDIR', 'root of qt directory' ),
+ ( 'QTLIBPATH', 'path to the qt libraries' ),
+ ( 'QTINCLUDEPATH', 'path to the qt includes' ),
+ ( 'QT_UIC', 'moc directory'),
+ ( 'QT_MOC', 'moc executable command'),
+ ( 'QTPLUGINS', 'uic executable command'),
+ ( 'KDEDIR', 'root of kde directory' ),
+ ( 'KDELIBPATH', 'path to the kde libs' ),
+ ( 'KDEINCLUDEPATH', 'path to the kde includes' ),
+
+ ( 'PREFIX', 'root of the program installation'),
+
+ ( 'KDEBIN', 'installation path of the kde binaries'),
+ ( 'KDEMODULE', 'installation path of the parts and libs'),
+ ( 'KDEAPPS', ''),
+ ( 'KDEDATA', 'installation path of the application data'),
+ ( 'KDELOCALE', ''),
+ ( 'KDEDOC', 'installation path of the application documentation'),
+ ( 'KDEKCFG', 'installation path of the .kcfg files'),
+ ( 'KDEXDG', 'installation path of the service types'),
+ ( 'KDEMENU', ''),
+ ( 'KDEMIME', 'installation path of to the mimetypes'),
+ ( 'KDEICONS', ''),
+ ( 'KDESERV', ''),
+ )
+ opts.Update(env)
+
+ # reconfigure when things are missing
+ if 'configure' in env['TARGS'] or not env.has_key('QTDIR') or not env.has_key('KDEDIR'):
+ detect_kde(env)
+
+ # finally save the configuration
+ opts.Save('kde.cache.py', env)
+
+ ## set default variables, one can override them in sconscript files
+ env.Append(CXXFLAGS = ['-I'+env['KDEINCLUDEPATH'], '-I'+env['QTINCLUDEPATH'] ])
+ env.Append(LIBPATH = [env['KDELIBPATH'], env['QTLIBPATH'] ])
+
+ env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 1
+
+ env['QT_AUTOSCAN'] = 1
+ env['QT_DEBUG'] = 0
+
+ env['QT_UIC_HFLAGS'] = '-L $QTPLUGINS -nounload'
+ env['QT_UIC_CFLAGS'] = '$QT_UIC_HFLAGS -tr tr2i18n'
+ env['QT_LIBS'] = 'qt-mt'
+
+ env['LIBTOOL_FLAGS'] = '--silent --mode=compile --tag=CXX'
+
+ env['QT_UICIMPLPREFIX'] = ''
+ env['QT_UICIMPLSUFFIX'] = '.cpp'
+ env['QT_MOCHPREFIX'] = ''
+ env['QT_MOCHSUFFIX'] = '.moc'
+ env['KDE_KCFG_IMPLPREFIX'] = ''
+ env['KDE_KCFG_IMPL_HSUFFIX'] = '.h'
+ env['KDE_KCFG_IMPL_CSUFFIX'] = '.cpp'
+ env['KDE_SKEL_IMPL_SUFFIX'] = '.skel'
+ env['MEINPROC'] = 'meinproc'
+ env['MSGFMT'] = 'msgfmt'
+
+
+ ###### ui file processing
+ def uicGenerator(target, source, env, for_signature):
+ act=[]
+ act.append('$QT_UIC $QT_UIC_HFLAGS -o '+target[0].path+' '+source[0].path)
+ act.append('rm -f ' +target[1].path)
+ act.append('echo \'#include <klocale.h>\' >> '+target[1].path)
+ act.append('echo \'#include <kdialog.h>\' >> '+target[1].path)
+ act.append('$QT_UIC $QT_UIC_CFLAGS -impl '+target[0].path+' -o '+target[1].path+'.tmp '+source[0].path)
+ act.append('cat '+target[1].path+'.tmp >> '+target[1].path)
+ act.append('rm -f '+target[1].path+'.tmp')
+ act.append('echo \'#include "' + target[2].name + '"\' >> '+target[1].path)
+ act.append('$QT_MOC -o '+target[2].path+' '+target[0].path)
+ return act
+
+ def uicEmitter(target, source, env):
+ adjustixes = SCons.Util.adjustixes
+ bs = SCons.Util.splitext(str(source[0].name))[0]
+ bs = os.path.join(str(target[0].get_dir()),bs)
+ # first target is automatically added by builder (.h file)
+ if len(target) < 2:
+ # second target is .cpp file
+ target.append(adjustixes(bs,
+ env.subst('$QT_UICIMPLPREFIX'),
+ env.subst('$QT_UICIMPLSUFFIX')))
+ if len(target) < 3:
+ # third target is .moc file
+ target.append(adjustixes(bs,
+ env.subst('$QT_MOCHPREFIX'),
+ env.subst('$QT_MOCHSUFFIX')))
+ return target, source
+
+ UIC_BUILDER = Builder(
+ generator = uicGenerator,
+ emitter = uicEmitter,
+ suffix = '.h',
+ src_suffix = '.ui' )
+
+ ###### moc file processing
+ env['QT_MOCCOM'] = ('$QT_MOC -o ${TARGETS[0]} $SOURCE')
+
+ MOC_BUILDER = Builder(
+ action = '$QT_MOCCOM',
+ suffix = '.moc',
+ src_suffix = '.h' )
+
+ MOCCPP_BUILDER = Builder(
+ action = '$QT_MOCCOM',
+ suffix = '_moc.cpp',
+ src_suffix = '.h' )
+
+ ###### kcfg file processing
+ def kcfgGenerator(target, source, env, for_signature):
+ act=[]
+ act.append('kconfig_compiler -d'+str(source[0].get_dir())+' '+source[1].path+' '+source[0].path)
+ return act
+
+ def kcfgEmitter(target, source, env):
+ adjustixes = SCons.Util.adjustixes
+ bs = SCons.Util.splitext(str(source[0].name))[0]
+ bs = os.path.join(str(target[0].get_dir()),bs)
+ # first target is automatically added by builder (.h file)
+ if len(target) < 2:
+ # second target is .cpp file
+ target.append(adjustixes(bs, env.subst('$KDE_KCFG_IMPLPREFIX'), env.subst('$KDE_KCFG_IMPL_CSUFFIX')))
+
+ # find_file(kcfgfile, (source[0].get_dir(),) ,SCons.Node.FS.default_fs)
+ if len(source) <2:
+ if not os.path.isfile(str(source[0])):
+ print RED+'kcfg file given'+str(source[0])+' does not exist !'+NORMAL
+ return target, source
+ kfcgfilename = ""
+ kcfgFileDeclRx = re.compile("^[fF]ile\s*=\s*(.+)\s*$")
+ for line in file(str(source[0]), "r").readlines():
+ match = kcfgFileDeclRx.match(line.strip())
+ if match:
+ kcfgfilename = match.group(1)
+ break
+ source.append( str(source[0].get_dir())+'/'+kcfgfilename )
+ return target, source
+
+ KCFG_BUILDER = Builder(
+ generator = kcfgGenerator,
+ emitter = kcfgEmitter,
+ suffix = '.h',
+ src_suffix = '.kcfgc' )
+
+ ###### dcop processing
+ def dcopGenerator(target, source, env, for_signature):
+ act=[]
+ act.append('dcopidl '+source[0].path+' > '+target[1].path+'|| ( rm -f '+target[1].path+' ; false )')
+ act.append('dcopidl2cpp --c++-suffix cpp --no-signals --no-stub '+target[1].path)
+ return act
+
+ def dcopEmitter(target, source, env):
+ bs = SCons.Util.splitext(str(source[0].name))[0]
+ bs = os.path.join(str(target[0].get_dir()),bs)
+ target.append(bs+'.kidl')
+ #target.append(bs+'_skel.cpp')
+ return target, source
+
+ DCOP_BUILDER = Builder(
+ generator = dcopGenerator,
+ emitter = dcopEmitter,
+ suffix = '_skel.cpp',
+ src_suffix = '.h' )
+
+ ###### documentation (meinproc) processing
+ MEINPROC_BUILDER = Builder(
+ action = '$MEINPROC --check --cache $TARGET $SOURCE',
+ suffix = '.cache.bz2',
+ src_suffix = '.docbook' )
+
+ ###### translation files builder
+ TRANSFILES_BUILDER = Builder(
+ action = '$MSGFMT $SOURCE -o $TARGET',
+ suffix = '.gmo',
+ src_suffix = '.po' )
+
+ ###### libtool file builder
+ def laGenerator(target, source, env, for_signature):
+ act=[]
+ act.append('echo "dlname=\''+source[0].name+'\'" > '+target[0].path)
+ act.append('echo "library_names=\''+source[0].name+' '+source[0].name+' '+source[0].name+'\'" >> '+target[0].path)
+ act.append('echo "old_library=\'\'">> '+target[0].path)
+ act.append('echo "dependency_libs=\'\'">> '+target[0].path)
+ act.append('echo "current=0">> '+target[0].path)
+ act.append('echo "age=0">> '+target[0].path)
+ act.append('echo "revision=0">> '+target[0].path)
+ act.append('echo "installed=yes">> '+target[0].path)
+ act.append('echo "shouldnotlink=no">> '+target[0].path)
+ act.append('echo "dlopen=\'\'">> '+target[0].path)
+ act.append('echo "dlpreopen=\'\'">> '+target[0].path)
+ act.append('echo "libdir=\''+env['KDEMODULE']+'\'" >> '+target[0].path)
+ return act
+
+ LA_BUILDER = Builder(
+ generator = laGenerator,
+ suffix = '.la',
+ src_suffix = '.so' )
+
+####### TODO : real libtool builder (but i hate libtool - ita)
+# def libtoolGenerator(target, source, env, for_signature):
+# act=[]
+# act.append('libtool $LIBTOOL_FLAGS $CXX $CXXFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS -c -o '+target[0].path+' '+source[0].path)
+# return act
+# LIBTOOL_BUILDER = Builder(
+# generator = libtoolGenerator,
+# suffix = '.lo',
+# src_suffix = '.cpp' )
+
+ ##### register the builders
+ env['BUILDERS']['Uic'] = UIC_BUILDER
+ env['BUILDERS']['Moc'] = MOC_BUILDER
+ env['BUILDERS']['Moccpp'] = MOCCPP_BUILDER
+ env['BUILDERS']['Dcop'] = DCOP_BUILDER
+ env['BUILDERS']['Kcfg'] = KCFG_BUILDER
+ env['BUILDERS']['LaFile'] = LA_BUILDER
+ #env['BUILDERS']['Libtool'] = LIBTOOL_BUILDER
+ env['BUILDERS']['Meinproc'] = MEINPROC_BUILDER
+ env['BUILDERS']['Transfiles'] = TRANSFILES_BUILDER
+
+ static_obj, shared_obj = SCons.Tool.createObjBuilders(env)
+ static_obj.src_builder.append('Uic')
+ shared_obj.src_builder.append('Uic')
+ static_obj.src_builder.append('Kcfg')
+ shared_obj.src_builder.append('Kcfg')
+ static_obj.src_builder.append('LaFile')
+ shared_obj.src_builder.append('LaFile')
+ static_obj.src_builder.append('Meinproc')
+ shared_obj.src_builder.append('Meinproc')
+ static_obj.src_builder.append('Transfiles')
+ shared_obj.src_builder.append('Transfiles')
+
+ ## find the files to moc, dcop, and link against kde and qt
+ env.AppendUnique(PROGEMITTER = [MetasourcesStatic], SHLIBEMITTER=[MetasourcesShared], LIBEMITTER =[MetasourcesStatic])
+
+ ###########################################
+ ## Handy helpers for building kde programs
+ ## You should not have to modify them ..
+
+ import SCons.Util
+ skel_ext = [".skel", ".SKEL"]
+ def KDEfiles(target, source, env):
+ """
+ Returns a list of files for scons (handles kde tricks like .skel)
+ It also makes custom checks against double includes like : ['file.ui', 'file.cpp']
+ (file.cpp is already included because of file.ui)
+ """
+ src=[]
+ ui_files=[]
+ kcfg_files=[]
+ skel_files=[]
+ other_files=[]
+
+ # For each file, check wether it is a dcop file or not, and create the complete list of sources
+ for file in source:
+ bs = SCons.Util.splitext(file)[0]
+ ext = SCons.Util.splitext(file)[1]
+ if ext in skel_ext:
+ env.Dcop(bs+'.h')
+ src.append(bs+'_skel.cpp')
+ else:
+ src.append(file)
+
+ if ext == '.ui':
+ ui_files.append(bs)
+ elif ext == '.kcfgc':
+ kcfg_files.append(bs)
+ elif ext == '.skel':
+ skel_files.append(bs)
+ else:
+ other_files.append(bs)
+
+ # Now check against newbie errors
+ for file in ui_files:
+ for ofile in other_files:
+ if ofile == file:
+ print RED+"WARNING: You have included "+file+".ui and another file of the same prefix"+NORMAL
+ print "Files generated by uic (file.h, file.cpp must not be included"
+ for file in kcfg_files:
+ for ofile in other_files:
+ if ofile == file:
+ print RED+"WARNING: You have included "+file+".kcfg and another file of the same prefix"+NORMAL
+ print "Files generated by kconfig_compiler (settings.h, settings.cpp) must not be included"
+ #for file in skel_files:
+ # for ofile in other_files:
+ # if ofile == file:
+ # print RED+"WARNING: source contain "+file+".skel and another file of the same prefix"+NORMAL
+ # print "Files generated automatically from .skel file must not be included (file.h, file.idl)"
+
+ return src
+
+ # Special trick for installing rpms ...
+ env['DESTDIR']=''
+ if 'install' in env['TARGS'] and os.environ.has_key('DESTDIR'):
+ env['DESTDIR']=os.environ['DESTDIR']+'/'
+ print CYAN+'** Enabling DESTDIR for the project **' + NORMAL + env['DESTDIR']
+
+ def KDEinstall(path, file, lenv):
+ """ Quick wrapper """
+ if 'install' in lenv['TARGS']:
+ lenv.Alias('install', lenv.Install( lenv['DESTDIR']+path, file ) )
+
+ def KDEinstallas(destfile, file, lenv):
+ """ Quick wrapper """
+ if 'install' in lenv['TARGS']:
+ lenv.Alias('install', lenv.InstallAs( lenv['DESTDIR']+destfile, file ) )
+
+ def KDEprogram(target, source, lenv):
+ """ Makes a kde program
+ The program is installed except if one sets env['NOAUTOINSTALL'] """
+ src = KDEfiles(target, source, lenv)
+ lenv.Program(target, src)
+ if not lenv.has_key('NOAUTOINSTALL'):
+ KDEinstall(env['KDEBIN'], target, lenv)
+
+ def KDEshlib(target, source, lenv):
+ """ Makes a shared library for kde (.la file for klibloader)
+ The library is installed except if one sets env['NOAUTOINSTALL'] """
+ src = KDEfiles(target, source, lenv)
+ lenv.SharedLibrary( target, src )
+ lenv.LaFile( target, target+'.so' )
+ if not lenv.has_key('NOAUTOINSTALL'):
+ KDEinstall(env['KDEMODULE'], target+'.so', lenv)
+ KDEinstall(env['KDEMODULE'], target+'.la', lenv)
+
+ def KDEstaticlib(target, source, lenv):
+ """ Makes a static library for kde - in practice you should not use static libraries
+ 1. they take more memory than shared ones
+ 2. makefile.am needed it because of stupid limitations
+ (cannot handle sources in separate folders - takes extra processing) """
+ src = KDEfiles(target, source, lenv)
+ lenv.StaticLibrary( target, src )
+# # do not install static libraries
+# if not lenv.has_key('NOAUTOINSTALL'):
+# KDEinstall(env['KDEMODULE'], target+'.a', lenv)
+
+ def KDEaddlibs(libs, lenv):
+ """ Helper function """
+ lenv.AppendUnique(LIBS = libs)
+
+ def KDEaddpaths(paths, lenv):
+ """ Helper function """
+ lenv.AppendUnique(CPPPATH = paths)
+
+ def KDElang(transfiles, lenv):
+ """ Process translations (.po files) in a po/ dir """
+ if not lenv['APPNAME']:
+ print "define lenv['APPNAME'] before using KDElang !!"
+ return
+ for lang in transfiles:
+ lenv.Transfiles( lang+'.po' )
+ KDEinstallas( lenv['KDELOCALE']+'/'+lang+'/LC_MESSAGES/'+lenv['APPNAME']+'.mo',
+ lang+'.gmo', lenv )
+
+ def KDEdoc(lang, file, lenv):
+ """ Install the documentation """
+ if not lenv['APPNAME']:
+ print "define lenv['APPNAME'] before using KDEdoc !!"
+ env.Exit(1)
+ KDEinstall( lenv['KDEDOC']+'/'+lang+'/'+lenv['APPNAME'], file, lenv )
+
+ # Export variables so that sconscripts in subdirectories can use them
+ env.Export("KDEprogram KDEshlib KDEaddpaths KDEaddlibs KDEinstall KDEinstallas KDElang KDEdoc")
+
diff --git a/scons/scons-mini.tar.bz2 b/scons/scons-mini.tar.bz2
new file mode 100644
index 0000000..0c1ce52
--- /dev/null
+++ b/scons/scons-mini.tar.bz2
Binary files differ
diff --git a/src/FAQ b/src/FAQ
new file mode 100644
index 0000000..5cefe5c
--- /dev/null
+++ b/src/FAQ
@@ -0,0 +1,13 @@
+================================================================================
+FAQ
+================================================================================
+This FAQ regards the source code and its layout.
+
+
+Q: Why reimplement code in the part rather than consolidate it with the app?
+A: Mainly to make the application start faster, loading a part is not free. But
+ also to make the part start faster as it has much lower requirements.
+ I admit the maintainance consequences aren't pretty, but the part has very
+ little code, so theoretically it should be easy to keep bug fixes in sync.
+
+=========1=========2=========3=========4=========5=========6=========7=========8 \ No newline at end of file
diff --git a/src/SConscript b/src/SConscript
new file mode 100644
index 0000000..31c1933
--- /dev/null
+++ b/src/SConscript
@@ -0,0 +1,21 @@
+
+############################
+## 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()
+
+myenv.SConscript( dirs = Split( "app part") )
+
+KDEinstall( env['KDEDATA']+'/codeine', '../misc/codeineui.rc', myenv )
+KDEinstall( env['KDEDATA']+'/konqueror/servicemenus', '../misc/codeine_play_dvd.desktop', myenv )
+KDEinstall( env['KDESERV'], '../misc/codeine_part.desktop', myenv )
+KDEinstall( env['KDEXDG'], '../misc/codeine.desktop', myenv )
+
+for size in ['16', '22', '32', '48', '64', '128']:
+ KDEinstallas( env['KDEICONS']+'/crystalsvg/'+size+'x'+size+'/apps/codeine.png', '../misc/cr'+size+'-app-codeine.png', myenv )
+
+#print env['KDECXXFLAGS'] \ No newline at end of file
diff --git a/src/TODO b/src/TODO
new file mode 100644
index 0000000..a8d427d
--- /dev/null
+++ b/src/TODO
@@ -0,0 +1,38 @@
+1.0.1
+ Volume state saving
+
+1.0.x
+ Improve error messages and error handling
+ Consolidate error handling code in part and application so we don't diverge
+ No audio icon in analyzer when no audio
+ Playback finished message (amaroK statusbar code?)
+ Mouse move to show toolbar in fullscreen mode
+ People are likely to use "F" keyboard shortcut or escape after film ends in fullscreen mode
+ but the popup of the menu prevents this, as for some reason Qt stops handling keyboard
+ shortcuts. We need a solution. The escape issue makes the popup menu less useful. So
+ escape with the menu should exit fullscreen too. All shortcuts in the menu should work.
+ Shortcuts not in the menu shouldn't work as this is a modal menu! Could be dangerous.
+ Consider adding quit to the fullscreen context menu always, or make the menubar show with
+ toolbar when mouse moves.
+ Add tooltips to PlayMedia dialog for recent file listview entries (directory info etc.)
+ Show zoom percentage in statusbar/OSD when resizing window, snap to 100%?
+ More feedback at playback end
+ Better loading/buffering status/feedback
+ Save prettyTitle with url and show that in recent file list,
+ - save m3u's? <-- needs thought
+ - mark remote files?
+ Support .srt files like Kaffeine/xine-ui does, ask berkus if stuck.
+ DVD fullscreen, DVD menu shows or something
+
+1.1
+ Volume button (not there by default)
+ Two entries in Konqueror: 1. open in new window 2. open in current window
+ Play from begin scroll up popup when resuming playback
+ Much better DVD support
+ Show length information in normal window
+ 'o' in fullscreen mode shows OSD of length and elapsed time info, <-- emulate mplayer
+
+ACTION
+ xine config dialog is modal
+REACTION
+ none, at least yet, it is far easier to maintain modal dialogs \ No newline at end of file
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
diff --git a/src/codeine.h b/src/codeine.h
new file mode 100644
index 0000000..24e0659
--- /dev/null
+++ b/src/codeine.h
@@ -0,0 +1,43 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#ifndef CODEINE_H
+#define CODEINE_H
+
+// try to keep this file light. It gets included by
+// practically every implementation and many headers
+
+namespace Engine
+{
+ enum State
+ {
+ Uninitialised = 0,
+ Empty = 1,
+ Loaded = 2,
+ Playing = 4,
+ Paused = 8,
+ TrackEnded = 16
+ };
+}
+
+class QWidget;
+
+namespace Analyzer
+{
+ static const int SCOPE_SIZE_EXP = 9;
+ static const int SCOPE_SIZE = 1 << SCOPE_SIZE_EXP;
+}
+
+namespace Codeine
+{
+ QWidget *mainWindow(); //defined in mainWindow.cpp
+}
+
+/// used by mainWindow.h and xineEngine.h
+int main( int, char** );
+
+#define APP_VERSION "1.0.1"
+#define APP_NAME "codeine"
+#define PRETTY_NAME "Codeine"
+
+#endif
diff --git a/src/debug.h b/src/debug.h
new file mode 100644
index 0000000..a6b9baa
--- /dev/null
+++ b/src/debug.h
@@ -0,0 +1,263 @@
+// Author: Max Howell <max.howell@methylblue.com>, (C) 2003-5
+// Copyright: See COPYING file that comes with this distribution
+//
+
+#ifndef CODEINE_DEBUG_H
+#define CODEINE_DEBUG_H
+
+#include <kdebug.h>
+#include <qcstring.h>
+#include <qvariant.h>
+#include <sys/time.h>
+
+class QApplication; ///@see Debug::Indent
+extern QApplication *qApp;
+
+
+/**
+ * @namespace Debug
+ * @short kdebug with indentation functionality and convenience macros
+ * @author Max Howell <max.howell@methylblue.com>
+ *
+ * Usage:
+ *
+ * #define DEBUG_PREFIX "Blah"
+ * #include "debug.h"
+ *
+ * void function()
+ * {
+ * Debug::Block myBlock( __PRETTY_FUNCTION__ );
+ *
+ * debug() << "output1" << endl;
+ * debug() << "output2" << endl;
+ * }
+ *
+ * Will output:
+ *
+ * app: BEGIN: void function()
+ * app: [Blah] output1
+ * app: [Blah] output2
+ * app: END: void function(): Took 0.1s
+ *
+ * @see Block
+ * @see CrashHelper
+ * @see ListStream
+ */
+
+
+namespace Debug
+{
+ inline QCString &indent()
+ {
+ static QCString indent;
+ return indent;
+ #if 0
+ static timeval *stamp = 0;
+
+ if( stamp == 0 ) {
+ stamp = new timeval;
+ return "[00:00] "; }
+
+ timeval now;
+ gettimeofday( &now, 0 );
+ now.tv_sec -= stamp->tv_sec;
+
+ QString time( "[%1:%2]" );
+
+ return time.arg( now.tv_sec / 60, 2 ).arg( now.tv_sec % 60, 2 ).latin1() + indent;
+ #endif
+ }
+
+ #ifdef NDEBUG
+ static inline kndbgstream debug() { return kndbgstream(); }
+ static inline kndbgstream warning() { return kndbgstream(); }
+ static inline kndbgstream error() { return kndbgstream(); }
+ static inline kndbgstream fatal() { return kndbgstream(); }
+
+ static inline void debug1( QVariant v ) {}
+
+ typedef kndbgstream Stream;
+ #else
+ #ifndef DEBUG_PREFIX
+ #define AMK_PREFIX ""
+ #else
+ #define AMK_PREFIX "[" DEBUG_PREFIX "] "
+ #endif
+
+ //from kdebug.h
+ enum DebugLevels {
+ KDEBUG_INFO = 0,
+ KDEBUG_WARN = 1,
+ KDEBUG_ERROR = 2,
+ KDEBUG_FATAL = 3
+ };
+
+ static inline kdbgstream debug() { return kdbgstream( indent(), 0, KDEBUG_INFO ) << AMK_PREFIX; }
+ static inline kdbgstream warning() { return kdbgstream( indent(), 0, KDEBUG_WARN ) << AMK_PREFIX << "[WARNING!] "; }
+ static inline kdbgstream error() { return kdbgstream( indent(), 0, KDEBUG_ERROR ) << AMK_PREFIX << "[ERROR!] "; }
+ static inline kdbgstream fatal() { return kdbgstream( indent(), 0, KDEBUG_FATAL ) << AMK_PREFIX; }
+
+ /// convenience function
+ static inline void debug1( QVariant v ) { kdbgstream( indent(), 0, KDEBUG_INFO ) << v << endl; }
+
+ typedef kdbgstream Stream;
+
+ #undef AMK_PREFIX
+ #endif
+
+ typedef kndbgstream DummyStream;
+}
+
+using Debug::debug;
+using Debug::debug1;
+
+/// Standard function announcer
+#define DEBUG_FUNC_INFO kdDebug() << Debug::indent() << k_funcinfo << endl;
+
+/// Announce a line
+#define DEBUG_LINE_INFO kdDebug() << Debug::indent() << k_funcinfo << "Line: " << __LINE__ << endl;
+
+/// Convenience macro for making a standard Debug::Block
+#define DEBUG_BLOCK Debug::Block uniquelyNamedStackAllocatedStandardBlock( __PRETTY_FUNCTION__ );
+
+#define DEBUG_INDENT Debug::indent() += " ";
+#define DEBUG_UNINDENT { QCString &s = Debug::indent(); s.truncate( s.length() - 2 ); }
+
+/// Use this to remind yourself to finish the implementation of a function
+#define DEBUG_NOTIMPLEMENTED warning() << "NOT-IMPLEMENTED: " << __PRETTY_FUNCTION__ << endl;
+
+/// Use this to alert other developers to stop using a function
+#define DEBUG_DEPRECATED warning() << "DEPRECATED: " << __PRETTY_FUNCTION__ << endl;
+
+
+namespace Debug
+{
+ /**
+ * @class Debug::Block
+ * @short Use this to label sections of your code
+ *
+ * Usage:
+ *
+ * void function()
+ * {
+ * Debug::Block myBlock( "section" );
+ *
+ * debug() << "output1" << endl;
+ * debug() << "output2" << endl;
+ * }
+ *
+ * Will output:
+ *
+ * app: BEGIN: section
+ * app: [prefix] output1
+ * app: [prefix] output2
+ * app: END: section - Took 0.1s
+ *
+ */
+
+ class Block
+ {
+ timeval m_start;
+ const char *m_label;
+
+ public:
+ Block( const char *label )
+ : m_label( label )
+ {
+ gettimeofday( &m_start, 0 );
+
+ kdDebug() << indent() << "BEGIN: " << label << "\n";
+ DEBUG_INDENT
+ }
+
+ ~Block()
+ {
+ timeval end;
+ gettimeofday( &end, 0 );
+
+ end.tv_sec -= m_start.tv_sec;
+ if( end.tv_usec < m_start.tv_usec) {
+ // Manually carry a one from the seconds field.
+ end.tv_usec += 1000000;
+ end.tv_sec--;
+ }
+ end.tv_usec -= m_start.tv_usec;
+
+ double duration = double(end.tv_sec) + (double(end.tv_usec) / 1000000.0);
+
+ DEBUG_UNINDENT
+ kdDebug() << indent() << "END__: " << m_label
+ << " - Took " << QString::number( duration, 'g', 3 ) << "s\n";
+ }
+ };
+
+
+ /**
+ * @name Debug::stamp()
+ * @short To facilitate crash/freeze bugs, by making it easy to mark code that has been processed
+ *
+ * Usage:
+ *
+ * {
+ * Debug::stamp();
+ * function1();
+ * Debug::stamp();
+ * function2();
+ * Debug::stamp();
+ * }
+ *
+ * Will output (assuming the crash occurs in function2()
+ *
+ * app: Stamp: 1
+ * app: Stamp: 2
+ *
+ */
+
+ inline void stamp()
+ {
+ static int n = 0;
+ debug() << "| Stamp: " << ++n << endl;
+ }
+}
+
+
+namespace Debug
+{
+ /**
+ * @class Debug::List
+ * @short You can pass anything to this and it will output it as a list
+ *
+ * debug() << (Debug::List() << anInt << aString << aQStringList << aDouble) << endl;
+ */
+
+ typedef QValueList<QVariant> List;
+}
+
+
+#include <kmessagebox.h>
+
+namespace Codeine
+{
+ //FIXME this function is inlined, so this may cause linkage problems for some people..
+ extern class VideoWindow* const videoWindow();
+
+ namespace MessageBox
+ {
+ static inline void error( const QString &message )
+ {
+ KMessageBox::error( (QWidget*)videoWindow(), message );
+ }
+
+ static inline void sorry( const QString &message )
+ {
+ KMessageBox::error( (QWidget*)videoWindow(), message );
+ }
+
+ static inline void information( const QString &message, const QString &title )
+ {
+ KMessageBox::information( (QWidget*)videoWindow(), message, title );
+ }
+ }
+}
+
+#endif
diff --git a/src/mxcl.library.cpp b/src/mxcl.library.cpp
new file mode 100644
index 0000000..597a548
--- /dev/null
+++ b/src/mxcl.library.cpp
@@ -0,0 +1,19 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#include "mxcl.library.h"
+#include <qapplication.h>
+#include <kcursor.h>
+
+namespace mxcl
+{
+ WaitCursor::WaitCursor()
+ {
+ QApplication::setOverrideCursor( KCursor::waitCursor() );
+ }
+
+ WaitCursor::~WaitCursor()
+ {
+ QApplication::restoreOverrideCursor();
+ }
+}
diff --git a/src/mxcl.library.h b/src/mxcl.library.h
new file mode 100644
index 0000000..feb71ef
--- /dev/null
+++ b/src/mxcl.library.h
@@ -0,0 +1,28 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#ifndef MXCL_LIBRARY_H
+#define MXCL_LIBRARY_H
+
+
+namespace mxcl
+{
+ /// Allocate on stack, wait cursor will be shown during existance
+ struct WaitCursor
+ {
+ WaitCursor();
+ ~WaitCursor();
+ };
+}
+
+
+/// almost always negates the need to #include <klocale.h> in implementations
+#include <qstring.h>
+QString i18n( const char *text );
+
+
+/// very useful for QStringLists
+#define foreach( x ) \
+ for( QStringList::ConstIterator it = x.constBegin(), end = x.constEnd(); it != end; ++it )
+
+#endif
diff --git a/src/part/SConscript b/src/part/SConscript
new file mode 100644
index 0000000..c2bce67
--- /dev/null
+++ b/src/part/SConscript
@@ -0,0 +1,12 @@
+# Copyright 2005 Max Howell <max.howell@methylblue.com>
+
+Import( "*" )
+myenv=env.Copy()
+
+## Additional paths for compiling the source files
+## Always add '../' (top-level directory) because moc makes code that needs it
+KDEaddpaths( ['./', '../', '../../'], myenv )
+
+KDEaddlibs( ['qt-mt', 'kdecore', 'kdeui', 'kparts', 'xine'], myenv )
+
+KDEshlib( "libcodeine", Split( "part.cpp xineEngine.cpp videoWindow.cpp toolbar.cpp ../mxcl.library.cpp" ), myenv )
diff --git a/src/part/part.cpp b/src/part/part.cpp
new file mode 100644
index 0000000..20d1577
--- /dev/null
+++ b/src/part/part.cpp
@@ -0,0 +1,83 @@
+// Author: Max Howell <max.howell@methylblue.com>, (C) 2005
+// Copyright: See COPYING file that comes with this distribution
+
+#include "codeine.h"
+#include "debug.h"
+#include <kaboutdata.h>
+#include <kparts/genericfactory.h>
+#include "part.h"
+#include <qtimer.h>
+#include "toolbar.h"
+#include "videoWindow.h"
+
+#include <kaction.h>
+#include <qslider.h>
+
+namespace Codeine
+{
+ typedef KParts::GenericFactory<Codeine::Part> Factory;
+}
+
+
+K_EXPORT_COMPONENT_FACTORY( libcodeine, Codeine::Factory )
+
+
+namespace Codeine
+{
+ Part::Part( QWidget *parentWidget, const char *widgetName, QObject *parent, const char *name, const QStringList& )
+ : ReadOnlyPart( parent, name )
+ , m_statusBarExtension( new KParts::StatusBarExtension( this ) )
+ {
+ setInstance( Factory::instance() );
+ setWidget( new VideoWindow( parentWidget, widgetName ) );
+
+ if( !videoWindow()->init() )
+ //FIXME this will terminate the host, eg Konqueror
+ Debug::fatal() << "Couldn't init xine!\n";
+
+ KAction *play = new KToggleAction( i18n("Play"), "player_play", Qt::Key_Space, videoWindow(), SLOT(togglePlay()), actionCollection(), "play" );
+ KAction *mute = new KToggleAction( i18n("Mute"), "player_mute", Qt::Key_M, videoWindow(), SLOT(toggleMute()), actionCollection(), "mute" );
+ KToolBar *toolBar = new MouseOverToolBar( widget() );
+ play->plug( toolBar );
+ mute->plug( toolBar );
+ m_slider = new QSlider( Qt::Horizontal, toolBar, "slider" );
+ m_slider->setMaxValue( 65535 );
+ toolBar->setStretchableWidget( m_slider );
+ toolBar->addSeparator(); //FIXME ugly
+
+ QObject *o = (QObject*)statusBar();
+ connect( videoWindow(), SIGNAL(statusMessage( const QString& )), o, SLOT(message( const QString& )) );
+ connect( videoWindow(), SIGNAL(titleChanged( const QString& )), o, SLOT(message( const QString& )) ); //FIXME
+ }
+
+ bool
+ Part::openURL( const KURL &url )
+ {
+ //FIXME nasty, we'd rather not do this way
+ killTimers();
+ startTimer( 100 );
+
+ return videoWindow()->play( m_url = url );
+ }
+
+ bool
+ Part::closeURL()
+ {
+ m_url = KURL();
+ videoWindow()->eject();
+ return true;
+ }
+
+ KAboutData*
+ Part::createAboutData()
+ {
+ // generic factory expects this on the heap
+ return new KAboutData( APP_NAME, PRETTY_NAME, APP_VERSION );
+ }
+
+ void
+ Part::timerEvent( QTimerEvent* )
+ {
+ m_slider->setValue( videoWindow()->position() );
+ }
+}
diff --git a/src/part/part.h b/src/part/part.h
new file mode 100644
index 0000000..30467ac
--- /dev/null
+++ b/src/part/part.h
@@ -0,0 +1,38 @@
+// Author: Max Howell <max.howell@methylblue.com>, (C) 2005
+// Copyright: See COPYING file that comes with this distribution
+
+#ifndef CODEINE_PART_H
+#define CODEINE_PART_H
+
+#include <kparts/statusbarextension.h>
+#include <kparts/part.h>
+#include <kurl.h>
+
+class KAboutData;
+class QSlider;
+
+
+namespace Codeine
+{
+ class Part : public KParts::ReadOnlyPart
+ {
+ public:
+ Part( QWidget*, const char*, QObject*, const char*, const QStringList& );
+
+ virtual bool openFile() { return false; } //pure virtual in base class
+ virtual bool openURL( const KURL& );
+ virtual bool closeURL();
+
+ static KAboutData *createAboutData();
+
+ private:
+ KParts::StatusBarExtension *m_statusBarExtension;
+ QSlider *m_slider;
+
+ KStatusBar *statusBar() { return m_statusBarExtension->statusBar(); }
+
+ virtual void timerEvent( QTimerEvent* );
+ };
+}
+
+#endif
diff --git a/src/part/toolbar.cpp b/src/part/toolbar.cpp
new file mode 100644
index 0000000..8939074
--- /dev/null
+++ b/src/part/toolbar.cpp
@@ -0,0 +1,44 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#include <kpushbutton.h>
+#include <qapplication.h>
+#include <qevent.h>
+#include "toolbar.h"
+
+
+MouseOverToolBar::MouseOverToolBar( QWidget *parent )
+ : KToolBar( parent )
+{
+ parent->installEventFilter( this );
+ move( 0, 0 ); //TODO necessary?
+ hide();
+
+ setPalette( QApplication::palette() ); //videoWindow palette has a black background
+}
+
+bool
+MouseOverToolBar::eventFilter( QObject *o, QEvent *e )
+{
+ Q_ASSERT( o == parent() );
+
+ switch( e->type() )
+ {
+ case QEvent::Resize:
+ resize( static_cast<QResizeEvent*>(e)->size().width(), sizeHint().height() );
+ break;
+
+ case QEvent::Enter:
+ show();
+ break;
+
+ case QEvent::Leave:
+ hide();
+ break;
+
+ default:
+ ;
+ }
+
+ return false;
+} \ No newline at end of file
diff --git a/src/part/toolbar.h b/src/part/toolbar.h
new file mode 100644
index 0000000..cd189d4
--- /dev/null
+++ b/src/part/toolbar.h
@@ -0,0 +1,18 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#ifndef CODEINE_TOOLBAR_H
+#define CODEINE_TOOLBAR_H
+
+#include <ktoolbar.h>
+
+
+class MouseOverToolBar : public KToolBar
+{
+ virtual bool eventFilter( QObject*, QEvent* );
+
+public:
+ MouseOverToolBar( QWidget *parent );
+};
+
+#endif
diff --git a/src/part/videoWindow.cpp b/src/part/videoWindow.cpp
new file mode 100644
index 0000000..d798b36
--- /dev/null
+++ b/src/part/videoWindow.cpp
@@ -0,0 +1,192 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#define CODEINE_DEBUG_PREFIX "videoWindow"
+
+#include <cstdlib>
+#include "debug.h"
+#include <qapplication.h> //sendEvent()
+#include <qcursor.h>
+#include <qevent.h>
+#include "videoWindow.h"
+#include <X11/Xlib.h> //TODO this breaks compile for lots of people due to excessive macro content
+#include <xine.h> //x11_visual_t
+
+
+namespace Codeine {
+
+
+VideoWindow *VideoWindow::s_instance = 0;
+
+
+namespace X
+{
+ Display *d;
+ int s, w;
+}
+
+
+VideoWindow::VideoWindow( QWidget *parent, const char *name )
+ : QWidget( parent, name )
+ , m_osd( 0 )
+ , m_stream( 0 )
+ , m_eventQueue( 0 )
+ , m_videoPort( 0 )
+ , m_audioPort( 0 )
+ , m_xine( 0 )
+ , m_displayRatio( 1 )
+{
+ s_instance = this;
+
+ // with this Konqueror would crash on exit
+ // without this we may be unstable!
+ //XInitThreads();
+
+ show();
+
+ setWFlags( Qt::WNoAutoErase );
+ setMouseTracking( true );
+ setAcceptDrops( true );
+ setUpdatesEnabled( false ); //to stop Qt drawing over us
+ setPaletteBackgroundColor( Qt::black );
+
+ 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;
+ }
+
+ XUnlockDisplay( X::d );
+
+ connect( &m_timer, SIGNAL(timeout()), SLOT(hideCursor()) );
+}
+
+VideoWindow::~VideoWindow()
+{
+ DEBUG_BLOCK
+
+ 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_videoPort ) xine_close_video_driver( m_xine, m_videoPort );
+ if( m_audioPort ) xine_close_audio_driver( m_xine, m_audioPort );
+ if( m_xine ) xine_exit( m_xine );
+
+ XCloseDisplay( X::d );
+}
+
+void*
+VideoWindow::x11Visual() const
+{
+ x11_visual_t* visual = new x11_visual_t;
+
+ visual->display = X::d;
+ visual->screen = X::s;
+ visual->d = 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
+}
+
+bool
+VideoWindow::event( QEvent *e )
+{
+ switch( e->type() )
+ {
+ case QEvent::MouseMove:
+ case QEvent::MouseButtonPress:
+ unsetCursor();
+ m_timer.start( CURSOR_HIDE_TIMEOUT, true );
+ break;
+
+ case QEvent::Close:
+ case QEvent::Hide:
+ xine_stop( m_stream );
+ break;
+
+ case QEvent::Leave:
+ m_timer.stop();
+ break;
+
+ default:
+ ;
+ }
+
+ return QWidget::event( e );
+}
+
+bool
+VideoWindow::x11Event( XEvent *e )
+{
+ if( 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 );
+}
+
+} //namespace Codeine
diff --git a/src/part/videoWindow.h b/src/part/videoWindow.h
new file mode 100644
index 0000000..7db72ab
--- /dev/null
+++ b/src/part/videoWindow.h
@@ -0,0 +1,94 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#ifndef CODEINE_VIDEO_WINDOW_H
+#define CODEINE_VIDEO_WINDOW_H
+
+#include "../codeine.h"
+#include <qtimer.h>
+#include <qwidget.h>
+#include <kurl.h>
+
+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 Codeine
+{
+ class VideoWindow : public QWidget
+ {
+ Q_OBJECT
+
+ static VideoWindow *s_instance;
+ static const uint CURSOR_HIDE_TIMEOUT = 2000;
+
+ friend VideoWindow* const videoWindow();
+
+ public:
+ VideoWindow( QWidget *parent, const char *name );
+ ~VideoWindow();
+
+ bool init();
+
+ bool play( KURL );
+ void eject();
+
+ int position();
+
+ signals:
+ void statusMessage( const QString& );
+ void titleChanged( const QString& );
+
+ private:
+ /// @see xineEngine.cpp
+ #ifdef HAVE_XINE_H
+ static void xineEventListener( void*, const xine_event_t* );
+ #endif
+
+ void showErrorMessage(); //TODO don't use this, just show delayed message
+
+ virtual void customEvent( QCustomEvent* );
+ virtual bool x11Event( XEvent* );
+ virtual bool event( QEvent* );
+
+ 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_t *m_xine;
+
+ KURL m_url;
+
+ private:
+ void *x11Visual() const;
+
+ static void destSizeCallBack( void*, int, int, double, int*, int*, double* );
+ static void frameOutputCallBack( void*, int, int, double, int*, int*, int*, int*, double*, int*, int* );
+
+ double m_displayRatio;
+ QTimer m_timer;
+
+ public slots:
+ void togglePlay();
+ void toggleMute();
+
+ private slots:
+ void hideCursor();
+
+ private:
+ /// prevent compiler generated functions
+ VideoWindow( const VideoWindow& );
+ VideoWindow &operator=( const VideoWindow& );
+ bool operator==( const VideoWindow& );
+ };
+
+ inline VideoWindow* const videoWindow() { return VideoWindow::s_instance; }
+}
+
+#endif
diff --git a/src/part/xineEngine.cpp b/src/part/xineEngine.cpp
new file mode 100644
index 0000000..2365f83
--- /dev/null
+++ b/src/part/xineEngine.cpp
@@ -0,0 +1,345 @@
+// (C) 2005 Max Howell (max.howell@methylblue.com)
+// See COPYING file for licensing information
+
+#define CODEINE_DEBUG_PREFIX "engine"
+
+#include "debug.h"
+#include <kglobalsettings.h>
+#include <klocale.h>
+#include "mxcl.library.h"
+#include <qapplication.h> //::sendEvent()
+#include <qdatetime.h> //::play()
+#include <qdir.h> //QDir::homeDir()
+#include <xine.h>
+#include "videoWindow.h"
+
+
+namespace Codeine {
+
+
+bool
+VideoWindow::init()
+{
+ mxcl::WaitCursor allocateOnStack;
+
+ debug() << "xine_new()\n";
+ m_xine = xine_new();
+ if( !m_xine )
+ return false;
+
+ 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, 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;
+
+ if( !m_audioPort )
+ MessageBox::error( i18n("xine was unable to initialize any audio-drivers.") );
+ if( !m_videoPort )
+ MessageBox::error( i18n("xine was unable to initialize any video-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 );
+ }
+
+ 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 save directory
+ xine_cfg_entry_t config;
+
+ if( xine_config_lookup_entry( m_xine, "misc.save_dir", &config ) ) {
+ const QCString dir = KGlobalSettings::desktopPath().local8Bit();
+ config.str_value = qstrdup( dir );
+ xine_config_update_entry( m_xine, &config );
+ }
+ }
+
+ return true;
+}
+
+bool
+VideoWindow::play( KURL url )
+{
+ DEBUG_BLOCK
+
+ m_url = url;
+
+ mxcl::WaitCursor allocateOnStack;
+
+ //TODO make sensible
+ if( url.protocol() == "http" ) {
+ /// automatically save http streams to Desktop folder
+
+ const QString fileName = url.filename();
+
+ QString
+ u = url.url();
+ u += "#save:";
+ u += url.host();
+ u += " [";
+ u += QDate::currentDate().toString();
+ u += ']';
+ u += fileName.mid( fileName.findRev( '.' ) + 1 ).lower();
+
+ url = u;
+ }
+
+ debug() << "About to load..\n";
+ if( xine_open( m_stream, url.url().local8Bit() ) )
+ {
+ debug() << "About to play..\n";
+ if( xine_play( m_stream, 0, 0 ) )
+ return true;
+ }
+
+ showErrorMessage();
+ return false;
+}
+
+void
+VideoWindow::togglePlay()
+{
+ if( xine_get_param( m_stream, XINE_PARAM_SPEED ) ) {
+ xine_set_param( m_stream, XINE_PARAM_SPEED, XINE_SPEED_PAUSE );
+ xine_set_param( m_stream, XINE_PARAM_AUDIO_CLOSE_DEVICE, 1);
+ }
+ else
+ xine_set_param( m_stream, XINE_PARAM_SPEED, XINE_SPEED_NORMAL );
+}
+
+void
+VideoWindow::toggleMute()
+{
+ bool const muted = xine_get_param( m_stream, XINE_PARAM_AUDIO_MUTE );
+ xine_set_param( m_stream, XINE_PARAM_AUDIO_MUTE, !muted );
+}
+
+void
+VideoWindow::eject()
+{
+ m_url = KURL();
+
+ xine_stop( m_stream );
+}
+
+int
+VideoWindow::position()
+{
+ int pos = 0, time = 0, length = 0;
+ xine_get_pos_length( m_stream, &pos, &time, &length );
+
+ return pos;
+}
+
+void
+VideoWindow::showErrorMessage()
+{
+ const QString filename = m_url.fileName();
+
+ debug() << "xine_get_error()\n";
+
+ // NOTE these error messages are somewhat customised
+ // relative to the main application
+ // This is because when embedded in some other application
+ // the error messages have no context, so we must say that we are a video player!
+
+ switch( xine_get_error( m_stream ) )
+ {
+ case XINE_ERROR_NO_INPUT_PLUGIN:
+ MessageBox::sorry( i18n("The Codeine video player could not find an input plugin for '%1'.").arg( filename ) );
+ break;
+ case XINE_ERROR_NO_DEMUX_PLUGIN:
+ MessageBox::sorry( i18n("The Codeine video player could not find a demux plugin for '%1'.").arg( filename ) );
+ break;
+ case XINE_ERROR_DEMUX_FAILED:
+ MessageBox::sorry( i18n("The Codeine video player failed to demux '%1'; please check your xine installation.").arg( filename ) );
+ break;
+ case XINE_ERROR_INPUT_FAILED:
+ case XINE_ERROR_MALFORMED_MRL:
+ case XINE_ERROR_NONE:
+ MessageBox::sorry( i18n("The Codeine video player reports an internal error; please check your xine installation.") );
+ break;
+ }
+}
+
+void
+VideoWindow::customEvent( QCustomEvent *e )
+{
+ switch( e->type() - 2000 ) {
+ case XINE_EVENT_UI_PLAYBACK_FINISHED:
+//FIXME emit stateChanged( Engine::TrackEnded );
+ break;
+
+ case 1000:
+ #define message static_cast<QString*>(e->data())
+ emit statusMessage( *message );
+ delete message;
+ break;
+
+ case 1001:
+ MessageBox::sorry( (*message).arg( "FIXME" ) ); //FIXME
+ 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_MRL_REFERENCE:
+ {
+ mxcl::WaitCursor allocateOnStack;
+ const char *mrl = ((xine_mrl_reference_data_t*)xineEvent->data)->mrl;
+
+ debug() << "XINE_EVENT_MRL_REFERENCE: " << mrl << endl;
+
+ if( xine_open( engine->m_stream, mrl ) )
+ xine_play( engine->m_stream, 0, 0 );
+
+ break;
+ }
+
+ case XINE_EVENT_UI_NUM_BUTTONS: debug() << "XINE_EVENT_UI_NUM_BUTTONS\n"; 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
+}
+
+} //namespace Codeine