https://snapshot.debian.org/package/codeine/1.0.1-3.dfsg-3.1/ Signed-off-by: Michele Calgaro <michele.calgaro@yahoo.it>pull/1/head
commit
5f44f7b187
@ -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
|
@ -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
|
||||
}
|
@ -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 :-)
|
@ -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)
|
@ -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
|
||||
|
@ -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
|
@ -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
|
@ -0,0 +1,10 @@
|
||||
[KFileDialog Settings]
|
||||
ShowPreviews=false
|
||||
|
||||
[MainWindow Toolbar dvdToolBar]
|
||||
Hidden=true
|
||||
IconText=IconTextRight
|
||||
Index=0
|
||||
|
||||
[MainWindow Toolbar mainToolBar]
|
||||
Index=1
|
@ -0,0 +1,33 @@
|
||||
<!DOCTYPE kpartgui>
|
||||
<kpartgui name="codeine" version="4">
|
||||
<MenuBar>
|
||||
<Menu name="file" noMerge="1"><text>&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>&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>
|
After Width: | Height: | Size: 7.3 KiB |
After Width: | Height: | Size: 870 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 3.5 KiB |
@ -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 ""
|
@ -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"
|
||||
|
@ -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()
|
@ -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)
|
||||
|
@ -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")
|
||||
|
Binary file not shown.
@ -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
|
@ -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']
|
@ -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
|
@ -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'] )
|
@ -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 );
|
||||
}
|
||||
}
|
@ -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
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
@ -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;
|
||||
}
|
@ -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
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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
|
@ -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
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
@ -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;
|
||||
}
|
@ -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;
|
||||
};
|
@ -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 );
|
||||
}
|
||||
}
|
@ -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
|
@ -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;
|
||||
}
|
@ -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
|
@ -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
|
@ -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 );
|
||||
}
|
||||
|
||||
}
|
@ -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
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
@ -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;
|
||||
}
|
@ -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
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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 §ionTitle, 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
|
||||
}
|
@ -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
|
@ -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 );
|
||||
}
|
||||
}
|
@ -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
|
@ -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
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
@ -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 _
|
||||
}
|
@ -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
|
@ -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
|
@ -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
|
@ -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;
|
||||
}
|
@ -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
|
@ -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
|
@ -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
|
@ -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();
|
||||
}
|
||||
}
|
@ -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
|
@ -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 )
|
@ -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() );
|
||||
}
|
||||
}
|
@ -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
|
@ -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;
|
||||
}
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
Loading…
Reference in new issue