From 66605c73afda749d19dac310d41f7a7241d6d00b Mon Sep 17 00:00:00 2001 From: gregory guy Date: Mon, 26 Apr 2021 18:17:21 +0200 Subject: [PATCH] Import original source code KDE3 Asciiquarium-0.3.2 from https://store.kde.org/p/1124051. KDE Asciiquarium is a screensaver based off Kirk Baucom's asciiquarium program (http://www.robobunny.com/projects/asciiquarium/). Code is GPL licensed, https://robobunny.com/projects/asciiquarium/gpl.txt Signed-off-by: gregory guy --- asciiquarium/SConstruct | 32 + asciiquarium/admin/generic.py | 506 +++++++++ asciiquarium/admin/kde.py | 821 +++++++++++++++ asciiquarium/admin/scons-mini.tar.bz2 | Bin 0 -> 58132 bytes asciiquarium/configure | 164 +++ asciiquarium/src/AASaverConfig.kcfgc | 4 + asciiquarium/src/Doxyfile | 1218 ++++++++++++++++++++++ asciiquarium/src/SConscript | 44 + asciiquarium/src/aasaver.cpp | 1256 +++++++++++++++++++++++ asciiquarium/src/aasaver.h | 199 ++++ asciiquarium/src/asciiquarium.desktop | 23 + asciiquarium/src/asciiquarium.kcfg | 14 + asciiquarium/src/doxygen-stylesheet.css | 309 ++++++ asciiquarium/src/frame.cpp | 204 ++++ asciiquarium/src/frame.h | 150 +++ asciiquarium/src/screen.cpp | 251 +++++ asciiquarium/src/screen.h | 171 +++ asciiquarium/src/settingswidget.ui | 50 + asciiquarium/src/sprite.cpp | 94 ++ asciiquarium/src/sprite.h | 207 ++++ 20 files changed, 5717 insertions(+) create mode 100644 asciiquarium/SConstruct create mode 100644 asciiquarium/admin/generic.py create mode 100644 asciiquarium/admin/kde.py create mode 100644 asciiquarium/admin/scons-mini.tar.bz2 create mode 100755 asciiquarium/configure create mode 100644 asciiquarium/src/AASaverConfig.kcfgc create mode 100644 asciiquarium/src/Doxyfile create mode 100644 asciiquarium/src/SConscript create mode 100644 asciiquarium/src/aasaver.cpp create mode 100644 asciiquarium/src/aasaver.h create mode 100644 asciiquarium/src/asciiquarium.desktop create mode 100644 asciiquarium/src/asciiquarium.kcfg create mode 100644 asciiquarium/src/doxygen-stylesheet.css create mode 100644 asciiquarium/src/frame.cpp create mode 100644 asciiquarium/src/frame.h create mode 100644 asciiquarium/src/screen.cpp create mode 100644 asciiquarium/src/screen.h create mode 100644 asciiquarium/src/settingswidget.ui create mode 100644 asciiquarium/src/sprite.cpp create mode 100644 asciiquarium/src/sprite.h diff --git a/asciiquarium/SConstruct b/asciiquarium/SConstruct new file mode 100644 index 00000000..61fe6e9e --- /dev/null +++ b/asciiquarium/SConstruct @@ -0,0 +1,32 @@ +#! /usr/bin/env python + +################################################################### +# LOAD THE ENVIRONMENT AND SET UP THE TOOLS +################################################################### + +## Load the builders in config +tools = [ 'default', 'generic', 'kde' ] +toolpath = [ './', './admin' ] + +# Required as part of SCons +env = Environment(tools = tools, toolpath = toolpath) + +# Pull in some default settings. +env.KDEuse("environ rpath nohelp") +#env.KDEuse("environ rpath lang_qt thread nohelp") + +# Export the environment so that SConscript files in subdirs can access it. +Export('env') + +################################################################### +# SCRIPTS FOR BUILDING THE TARGETS +################################################################### + +distClean = env.Action("find %s -name '*.pyc' -exec rm {} \\;" % env.GetLaunchDir()) +dist = env.Alias('dist', action = distClean) + +env.Alias(dist, action = Delete("%s/cache" % env.GetLaunchDir())) +env.Clean(dist, ['cache']) +env.AlwaysBuild(dist) + +env.subdirs('src') diff --git a/asciiquarium/admin/generic.py b/asciiquarium/admin/generic.py new file mode 100644 index 00000000..3c8c993b --- /dev/null +++ b/asciiquarium/admin/generic.py @@ -0,0 +1,506 @@ +## 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[1m" #"\033[93m" # unreadable on white backgrounds +CYAN ="\033[96m" +NORMAL ="\033[0m" + +import os, re, types, sys, string, shutil, stat + +import SCons.Defaults +import SCons.Tool +import SCons.Util +from SCons.Script.SConscript import SConsEnvironment +from SCons.Options import Options, PathOption + +class genobj: + def __init__(self, val, env): + if not val in "program shlib kioslave staticlib".split(): + print "unknown genobj given: "+val + env.Exit(1) + + self.type = val + self.orenv = env + self.env = None + self.executed = 0 + + self.target='' + self.src=None + + self.cxxflags='' + self.cflags='' + self.includes='' + + self.linkflags='' + self.libpaths='' + self.libs='' + + # vars used by shlibs + self.vnum='' + self.libprefix='' + + # a directory where to install the targets (optional) + self.instdir='' + # ignore the DESTDIR (optional) + self.nodestdir='' + + # change the working directory before reading the targets + self.chdir='' + + # these members are private + self.chdir_lock=None + self.old_os_dir='' + self.old_fs_dir='' + self.p_local_shlibs=[] + self.p_local_staticlibs=[] + self.p_global_shlibs=[] + + #if not env.has_key('USE_THE_FORCE_LUKE'): env['USE_THE_FORCE_LUKE']=[self] + #else: env['USE_THE_FORCE_LUKE'].append(self) + + def lockchdir(self): + if not self.chdir: return + self.chdir_lock=1 + SConfFS=SCons.Node.FS.default_fs + self.old_fs_dir=SConfFS.getcwd() + self.old_os_dir=os.getcwd() + #os.chdir(old_os_dir+'/'+self.chdir) + SConfFS.chdir( SConfFS.Dir('#/'+self.chdir), change_os_dir=1) + + def unlockchdir(self): + if not self.chdir: return + if self.chdir_lock: + #os.chdir(self.old_os_dir) + SCons.Node.FS.default_fs.chdir(self.old_fs_dir, change_os_dir=0) + self.chdir_lock=None + + def execute(self): + if self.orenv.has_key('DUMPCONFIG'): + print self.xml() + return + + self.lockchdir() + + self.env = self.orenv.Copy() + + if not self.src or len(self.src) == 0: + print RED+"no source file given to object - self.src"+NORMAL + self.env.Exit(1) + if not self.env.has_key('nosmart_includes'): self.env.AppendUnique(CPPPATH=['./']) + if self.type == "kioslave": self.libprefix='' + + if len(self.includes)>0: self.env.AppendUnique(CPPPATH=self.env.make_list(self.includes)) + if len(self.cxxflags)>0: self.env.AppendUnique(CXXFLAGS=self.env.make_list(self.cxxflags)) + if len(self.cflags)>0: self.env.AppendUnique(CCFLAGS=self.env.make_list(self.cflags)) + + llist=self.env.make_list(self.libs) + lext='.so .la'.split() + sext='.a'.split() + for l in llist: + sal=SCons.Util.splitext(l) + if len(sal)>1: + if sal[1] in lext: self.p_local_shlibs.append(sal[0]+'.so') + elif sal[1] in sext: self.p_local_staticlibs.append(sal[0]+'.a') + else: self.p_global_shlibs.append(l) + + if len(self.p_global_shlibs)>0: self.env.AppendUnique(LIBS=self.p_global_shlibs) + if len(self.libpaths)>0: self.env.PrependUnique(LIBPATH=self.env.make_list(self.libpaths)) + if len(self.linkflags)>0: self.env.PrependUnique(LINKFLAGS=self.env.make_list(self.linkflags)) + + # the target to return + ret=None + if self.type=='shlib' or self.type=='kioslave': + ret=self.env.bksys_shlib(self.target, self.src, self.instdir, + self.libprefix, self.vnum, nodestdir=self.nodestdir) + elif self.type=='program': + ret=self.env.Program(self.target, self.src) + if not self.env.has_key('NOAUTOINSTALL'): + self.env.bksys_install(self.instdir, ret, nodestdir=self.nodestdir) + elif self.type=='staticlib': + ret=self.env.StaticLibrary(self.target, self.src) + + # we link the program against a shared library made locally, add the dependency + if len(self.p_local_shlibs)>0: + self.env.link_local_shlib(self.p_local_shlibs) + if ret: self.env.Depends( ret, self.p_local_shlibs ) + if len(self.p_local_staticlibs)>0: + self.env.link_local_staticlib(self.p_local_staticlibs) + if ret: self.env.Depends( ret, self.p_local_staticlibs ) + + self.unlockchdir() + +## Copy function that honors symlinks +def copy_bksys(dest, source, env): + if os.path.islink(source): + #print "symlinking "+source+" "+dest + if os.path.islink(dest): + os.unlink(dest) + os.symlink(os.readlink(source), dest) + else: + shutil.copy2(source, dest) + st=os.stat(source) + os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE) + return 0 + +## Return a list of things +def make_list(env, s): + if type(s) is types.ListType: + return s + else: + return s.split() + +def exists(env): + return true + +def generate(env): + ## Bksys requires scons 0.96 + env.EnsureSConsVersion(0, 96) + + SConsEnvironment.make_list = make_list + + env['HELP']=0 + if '--help' in sys.argv or '-h' in sys.argv or 'help' in sys.argv: + env['HELP']=1 + + if env['HELP']: + print """ +\033[1m*** Instructions *** +--------------------\033[0m +\033[1m* scons \033[0m: to compile +\033[1m* scons -j4 \033[0m: to compile with several instances +\033[1m* scons install \033[0m: to compile and install +\033[1m* scons -c install \033[0m: to uninstall + +\033[1m*** Generic options *** +-----------------------\033[0m +\033[1m* debug \033[0m: debug=1 (-g) or debug=full (-g3, slower) else use environment CXXFLAGS, or -O2 by default +\033[1m* prefix \033[0m: the installation path +\033[1m* extraincludes \033[0m: a list of paths separated by ':' +\033[1mscons configure debug=full prefix=/usr/local extraincludes=/tmp/include:/usr/local\033[0m +""" + return + + ## Global cache directory + # Put all project files in it so a rm -rf cache will clean up the config + if not env.has_key('CACHEDIR'): + env['CACHEDIR'] = os.getcwd()+'/cache/' + if not os.path.isdir(env['CACHEDIR']): + os.mkdir(env['CACHEDIR']) + + ## SCons cache directory + # This avoids recompiling the same files over and over again: + # very handy when working with cvs + if os.getuid() != 0: + env.CacheDir(os.getcwd()+'/cache/objects') + + # Avoid spreading .sconsign files everywhere - keep this line + env.SConsignFile(env['CACHEDIR']+'/scons_signatures') + + def makeHashTable(args): + table = { } + for arg in args: + if len(arg) > 1: + lst=arg.split('=') + if len(lst) < 2: + continue + key=lst[0] + value=lst[1] + if len(key) > 0 and len(value) >0: + table[key] = value + return table + + env['ARGS']=makeHashTable(sys.argv) + + ## Special trick for installing rpms ... + env['DESTDIR']='' + if 'install' in sys.argv: + dd='' + if os.environ.has_key('DESTDIR'): + dd=os.environ['DESTDIR'] + if not dd: + if env['ARGS']: dd=env['ARGS']['DESTDIR'] + if dd: + env['DESTDIR']=dd+'/' + print CYAN+'** Enabling DESTDIR for the project ** ' + NORMAL + env['DESTDIR'] + + ## install symlinks for shared libraries properly + env['INSTALL'] = copy_bksys + + ## Use the same extension .o for all object files + env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 1 + + ## load the options + cachefile=env['CACHEDIR']+'generic.cache.py' + opts = Options(cachefile) + opts.AddOptions( + ( 'GENCCFLAGS', 'C flags' ), + ( 'GENCXXFLAGS', 'debug level for the project : full or just anything' ), + ( 'GENLINKFLAGS', 'additional link flags' ), + ( 'PREFIX', 'prefix for installation' ), + ( 'EXTRAINCLUDES', 'extra include paths for the project' ), + ( 'ISCONFIGURED', 'is the project configured' ), + ) + opts.Update(env) + + # Use this to avoid an error message 'how to make target configure ?' + env.Alias('configure', None) + + # Check if the following command line arguments have been given + # and set a flag in the environment to show whether or not it was + # given. + if 'install' in sys.argv: + env['_INSTALL']=1 + else: + env['_INSTALL']=0 + if 'configure' in sys.argv: + env['_CONFIGURE']=1 + else: + env['_CONFIGURE']=0 + + # Configure the environment if needed + if not env['HELP'] and (env['_CONFIGURE'] or not env.has_key('ISCONFIGURED')): + + # be paranoid, unset existing variables + for var in "GENCXXFLAGS GENCCFLAGS GENLINKFLAGS PREFIX EXTRAINCLUDES ISCONFIGURED EXTRAINCLUDES".split(): + if env.has_key(var): env.__delitem__(var) + + if env['ARGS'].get('debug', None): + debuglevel = env['ARGS'].get('debug', None) + print CYAN+'** Enabling debug for the project **' + NORMAL + if debuglevel == "full": + env['GENCXXFLAGS'] = ['-DDEBUG', '-g3', '-Wall'] + else: + env['GENCXXFLAGS'] = ['-DDEBUG', '-g', '-Wall'] + else: + if os.environ.has_key('CXXFLAGS'): + # user-defined flags (gentooers will be elighted) + env['GENCXXFLAGS'] = SCons.Util.CLVar( os.environ['CXXFLAGS'] ) + env.Append( GENCXXFLAGS = ['-DNDEBUG', '-DNO_DEBUG'] ) + else: + env.Append(GENCXXFLAGS = ['-O2', '-DNDEBUG', '-DNO_DEBUG']) + + # Disable some unnecessary warnings. + env.Append(GENCXXFLAGS = ['-Wno-non-virtual-dtor', '-Wno-sign-compare', '-Wno-trigraphs']) + + if os.environ.has_key('CFLAGS'): + env['GENCCFLAGS'] = SCons.Util.CLVar( os.environ['CFLAGS'] ) + + ## FreeBSD settings (contributed by will at freebsd dot org) + if os.uname()[0] == "FreeBSD": + if os.environ.has_key('PTHREAD_LIBS'): + env.AppendUnique( GENLINKFLAGS = SCons.Util.CLVar( os.environ['PTHREAD_LIBS'] ) ) + else: + syspf = os.popen('/sbin/sysctl kern.osreldate') + osreldate = int(syspf.read().split()[1]) + syspf.close() + if osreldate < 500016: + env.AppendUnique( GENLINKFLAGS = ['-pthread']) + env.AppendUnique( GENCXXFLAGS = ['-D_THREAD_SAFE']) + elif osreldate < 502102: + env.AppendUnique( GENLINKFLAGS = ['-lc_r']) + env.AppendUnique( GENCXXFLAGS = ['-D_THREAD_SAFE']) + else: + env.AppendUnique( GENLINKFLAGS = ['-pthread']) + + # User-specified prefix + if env['ARGS'].has_key('prefix'): + env['PREFIX'] = os.path.abspath( env['ARGS'].get('prefix', '') ) + print (CYAN+'** installation prefix for the project set to : ' + + env['PREFIX'] +' **'+ NORMAL) + + # User-specified include paths + env['EXTRAINCLUDES'] = env['ARGS'].get('extraincludes', None) + if env['EXTRAINCLUDES']: + print (CYAN+'** extra include paths for the project set to: ' + + env['EXTRAINCLUDES'] +' **'+ NORMAL) + + env['ISCONFIGURED']=1 + + # And finally save the options in the cache + opts.Save(cachefile, env) + + def bksys_install(lenv, subdir, files, destfile=None, nodestdir=None): + """ Install files on "scons install" + If the DESTDIR env variable has been set, (e.g. by + "scons install DESTDIR=$CURDIR/debian) then install files to that + directory, regardless of where the configure stage showed that + files should be installed. + This feature is useful for packagers, and users of GNU stow. + + NB. The DESTDIR will be ignored if NODESTDIR is also set, although + the same effect can be acheived by not setting DESTDIR in the first + place.""" + + if not env['_INSTALL']: + return + basedir = env['DESTDIR'] + if nodestdir or env.has_key('NODESTDIR') : basedir = "/" + install_list = None + if not destfile: + install_list = env.Install(basedir+subdir+'/', files) + else: + if subdir: + install_list = env.InstallAs(basedir+subdir+'/'+destfile, files) + else: + install_list = env.InstallAs(basedir+'/'+destfile, files) + env.Alias('install', install_list) + return install_list + + def build_la_file(target, source, env): + """ Action for building libtool files. + Writes a .la file, as used by libtool.""" + dest=open(target[0].path, 'w') + sname=source[0].name + dest.write("dlname='%s'\n" % sname) + if len(env['BKSYS_VNUM'])>0: + vnum=env['BKSYS_VNUM'] + nums=vnum.split('.') + src=source[0].name + name = src.split('so.')[0] + 'so' + strn = src+" "+name+"."+str(nums[0])+" "+name + dest.write("library_names='%s'\n" % (strn) ) + else: + dest.write("library_names='%s %s %s'\n" % (sname, sname, sname) ) + dest.write("old_library=''\ndependency_libs=''\ncurrent=0\n") + dest.write("age=0\nrevision=0\ninstalled=yes\nshouldnotlink=no\n") + dest.write("dlopen=''\ndlpreopen=''\n") + dest.write("libdir='%s'" % env['BKSYS_DESTDIR']) + dest.close() + return 0 + + def string_la_file(target, source, env): + print "building '%s' from '%s'" % (target[0].name, source[0].name) + la_file = env.Action(build_la_file, string_la_file, ['BKSYS_VNUM', 'BKSYS_DESTDIR']) + env['BUILDERS']['LaFile'] = env.Builder(action=la_file,suffix='.la',src_suffix=env['SHLIBSUFFIX']) + + ## Function for building shared libraries + def bksys_shlib(lenv, target, source, libdir, libprefix='lib', vnum='', noinst=None, nodestdir=None): + """ Install a shared library. + + Installs a shared library, with or without a version number, and create a + .la file for use by libtool. + + If library version numbering is to be used, the version number + should be passed as a period-delimited version number (e.g. + vnum = '1.2.3'). This causes the library to be installed + with its full version number, and with symlinks pointing to it. + + For example, for libfoo version 1.2.3, install the file + libfoo.so.1.2.3, and create symlinks libfoo.so and + libfoo.so.1 that point to it. + """ + thisenv = lenv.Copy() # copying an existing environment is cheap + thisenv['BKSYS_DESTDIR']=libdir + thisenv['BKSYS_VNUM']=vnum + thisenv['SHLIBPREFIX']=libprefix + + if len(vnum)>0: + thisenv['SHLIBSUFFIX']='.so.'+vnum + thisenv.Depends(target, thisenv.Value(vnum)) + + # Fix against a scons bug - shared libs and ordinal out of range(128) + if type(source) is types.ListType: + src2=[] + for i in source: + src2.append( str(i) ) + source=src2 + + library_list = thisenv.SharedLibrary(target, source) + lafile_list = thisenv.LaFile(target, library_list) + + ## Install the libraries automatically + if not thisenv.has_key('NOAUTOINSTALL') and not noinst: + thisenv.bksys_install(libdir, library_list, nodestdir=nodestdir) + thisenv.bksys_install(libdir, lafile_list, nodestdir=nodestdir) + + ## Handle the versioning + if len(vnum)>0: + nums=vnum.split('.') + symlinkcom = ('cd $TARGET.dir && ' + + 'rm -f $TARGET.name && ' + + 'ln -s $SOURCE.name $TARGET.name') + tg = target+'.so.'+vnum + nm1 = target+'.so' + nm2 = target+'.so.'+nums[0] + + thisenv.Command(nm1, tg, symlinkcom) + thisenv.Command(nm2, tg, symlinkcom) + + #base=env['DESTDIR']+libdir+'/' + thisenv.bksys_install(libdir, nm1, nodestdir=nodestdir) + thisenv.bksys_install(libdir, nm2, nodestdir=nodestdir) + + # Declare scons scripts to process + def subdirs(lenv, folderlist): + flist=[] + if type(folderlist) is types.ListType: flist = folderlist + else: flist = folderlist.split() + for i in flist: + lenv.SConscript(i+"/SConscript") + + def link_local_shlib(lenv, str): + """ Links against a shared library made in the project """ + lst = lenv.make_list(str) + for file in lst: + import re + reg = re.compile("(.*)/lib(.*).(la|so)") + result = reg.match(file) + if not result: + print "Unknown la file given "+file + continue + dir = result.group(1) + link = result.group(2) + lenv.AppendUnique(LIBS = [link]) + lenv.PrependUnique(LIBPATH = [dir]) + + def link_local_staticlib(lenv, str): + """ Links against a shared library made in the project """ + lst = lenv.make_list(str) + for file in lst: + import re + reg = re.compile("(.*)/(lib.*.a)") + result = reg.match(file) + if not result: + print "Unknown archive file given "+file + continue + + f=SCons.Node.FS.default_fs.File(file) + lenv.Append(LINKFLAGS=[f.path]) + + #valid_targets = "program shlib kioslave staticlib".split() + SConsEnvironment.bksys_install = bksys_install + SConsEnvironment.bksys_shlib = bksys_shlib + SConsEnvironment.subdirs = subdirs + SConsEnvironment.link_local_shlib = link_local_shlib + SConsEnvironment.link_local_staticlib = link_local_staticlib + + SConsEnvironment.genobj=genobj + + if env.has_key('GENCXXFLAGS'): + env.AppendUnique( CPPFLAGS = env['GENCXXFLAGS'] ) + + if env.has_key('GENCCFLAGS'): + env.AppendUnique( CCFLAGS = env['GENCCFLAGS'] ) + + if env.has_key('GENLINKFLAGS'): + env.AppendUnique( LINKFLAGS = env['GENLINKFLAGS'] ) + + if env.has_key('EXTRAINCLUDES'): + if env['EXTRAINCLUDES']: + incpaths = [] + for dir in str(env['EXTRAINCLUDES']).split(':'): + incpaths.append( dir ) + env.Append(CPPPATH = incpaths) + + env.Export('env') diff --git a/asciiquarium/admin/kde.py b/asciiquarium/admin/kde.py new file mode 100644 index 00000000..9014d17b --- /dev/null +++ b/asciiquarium/admin/kde.py @@ -0,0 +1,821 @@ +# Made from scons qt.py and (heavily) modified into kde.py +# Thomas Nagy, 2004, 2005 + +""" +Run scons -h to display the associated help, or look below .. +""" + +BOLD ="\033[1m" +RED ="\033[91m" +GREEN ="\033[92m" +YELLOW ="\033[1m" #"\033[93m" # unreadable on white backgrounds +CYAN ="\033[96m" +NORMAL ="\033[0m" + +import os, re, types +from SCons.Script.SConscript import SConsEnvironment + +# Returns the name of the shared object (i.e. libkdeui.so.4) +# referenced by a libtool archive (like libkdeui.la) +def getSOfromLA(lafile): + contents = open(lafile, 'r').read() + match = re.search("^dlname='([^']*)'$", contents, re.M) + if match: + return match.group(1) + return None + +# A helper, needed .. everywhere +def KDEuse(lenv, flags): + if lenv['HELP']: lenv.Exit(0) + + _flags=lenv.make_list(flags) + if 'environ' in _flags: + ## The scons developers advise against using this but it is mostly innocuous :) + lenv.AppendUnique( ENV = os.environ ) + if not 'lang_qt' in _flags: + ## Use this define if you are using the kde translation scheme (.po files) + lenv.Append( CPPFLAGS = '-DQT_NO_TRANSLATION' ) + if 'rpath' in _flags: + ## Use this to set rpath - this may cause trouble if folders are moved (chrpath) + lenv.Append( RPATH = [lenv['QTLIBPATH'], lenv['KDELIBPATH'], lenv['KDEMODULE']] ) + kdelibpaths=[] + if lenv['KDELIBPATH'] == lenv['KDELIB']: + kdelibpaths = [lenv['KDELIB']] + else: + kdelibpaths = [lenv['KDELIBPATH'], lenv['KDELIB']] + lenv.Append( RPATH = [lenv['QTLIBPATH'], lenv['KDEMODULE']]+kdelibpaths ) + if 'thread' in _flags: + ## Uncomment the following if you need threading support + lenv.KDEaddflags_cxx( ['-DQT_THREAD_SUPPORT', '-D_REENTRANT'] ) + if 'fastmoc' in _flags: + lenv['BKSYS_FASTMOC']=1 + if not 'nohelp' in _flags: + if lenv['_CONFIGURE'] or lenv['HELP']: + lenv.Exit(0) + if not 'nosmart' or not lenv.has_key('nosmart_includes'): + lenv.AppendUnique(CPPPATH=['#/']) + lst=[] + if lenv.has_key('USE_THE_FORCE_LUKE'): + lst=lenv['USE_THE_FORCE_LUKE'] + lenv.__delitem__('USE_THE_FORCE_LUKE') + for v in lst: + v.execute() + else: + lenv['nosmart_includes']=1 + + ## To use kdDebug(intvalue)<<"some trace"</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?)" + env.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: + try: + tmplibdir = os.popen('kde-config --expandvars --install lib').read().strip() + libkdeuiSO = tmplibdir+'/'+getSOfromLA(tmplibdir+'/libkdeui.la') + m = re.search('(.*)/lib/libqt.*', os.popen('ldd ' + libkdeuiSO + ' | grep libqt').read().strip().split()[2]) + except: + m=None + 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?) or try scons -h for more options"+NORMAL + env.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 + env.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 + env.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 + env.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 + env.Exit(1) + + # kde-config options + kdec_opts = {'KDEBIN' : 'exe', 'KDEAPPS' : 'apps', + 'KDEDATA' : 'data', 'KDEICONS' : 'icon', + 'KDEMODULE' : 'module', 'KDELOCALE' : 'locale', + 'KDEKCFG' : 'kcfg', 'KDEDOC' : 'html', + 'KDEMENU' : 'apps', 'KDEXDG' : 'xdgdata-apps', + 'KDEMIME' : 'mime', 'KDEXDGDIR' : 'xdgdata-dirs', + 'KDESERV' : 'services','KDESERVTYPES' : 'servicetypes', + 'KDEINCLUDE': 'include' + } + + 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['PREFIX'] = prefix + env['KDELIB'] = libdir + for (var, option) in kdec_opts.items(): + dir = os.popen('kde-config --install ' + option).read().strip() + if var == 'KDEDOC': dir = debian_fix(dir) + env[var] = subst_vars(dir) + + else: + env['PREFIX'] = os.popen('kde-config --expandvars --prefix').read().strip() + env['KDELIB'] = os.popen('kde-config --expandvars --install lib').read().strip() + for (var, option) in kdec_opts.items(): + dir = os.popen('kde-config --expandvars --install ' + option).read().strip() + env[var] = dir + + 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"+libsuffix + env['QTLIBPATH']=qtlibs + +def generate(env): + """"Set up the qt and kde environment and builders - the moc part is difficult to understand """ + + # attach this function immediately + SConsEnvironment.KDEuse = KDEuse + + if env['HELP']: + print """\033[1m*** KDE options *** +-------------------\033[0m +\033[1m* prefix \033[0m: base install path, ie: /usr/local +\033[1m* execprefix \033[0m: install path for binaries, ie: /usr/bin +\033[1m* datadir \033[0m: install path for the data, ie: /usr/local/share +\033[1m* libdir \033[0m: install path for the libs, ie: /usr/lib +\033[1m* libsuffix \033[0m: suffix of libraries on amd64, ie: 64, 32 +\033[1m* kdeincludes\033[0m: path to the kde includes (/usr/include/kde on debian, ...) +\033[1m* qtincludes \033[0m: same punishment, for qt includes (/usr/include/qt on debian, ...) +\033[1m* kdelibs \033[0m: path to the kde libs, for linking the programs +\033[1m* qtlibs \033[0m: same punishment, for qt libraries +ie: \033[1mscons configure libdir=/usr/local/lib qtincludes=/usr/include/qt\033[0m +""" + return + + import SCons.Defaults + import SCons.Tool + import SCons.Util + import SCons.Node + + 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 + cachefile=env['CACHEDIR']+'kde.cache.py' + opts = Options(cachefile) + opts.AddOptions( + ('PREFIX', 'root of the program installation'), + + ('QTDIR', ''), + ('QTLIBPATH', 'path to the qt libraries'), + ('QTINCLUDEPATH', 'path to the qt includes'), + ('QT_UIC', 'uic command'), + ('QT_MOC', 'moc command'), + ('QTPLUGINS', 'uic executable command'), + + ('KDEDIR', ''), + ('KDELIBPATH', 'path to the installed kde libs'), + ('KDEINCLUDEPATH', 'path to the installed kde includes'), + + ('KDEBIN', 'inst path of the kde binaries'), + ('KDEINCLUDE', 'inst path of the kde include files'), + ('KDELIB', 'inst path of the kde libraries'), + ('KDEMODULE', 'inst path of the parts and libs'), + ('KDEDATA', 'inst path of the application data'), + ('KDELOCALE', ''), ('KDEDOC', ''), ('KDEKCFG', ''), + ('KDEXDG', ''), ('KDEXDGDIR', ''), ('KDEMENU', ''), + ('KDEMIME', ''), ('KDEICONS', ''), ('KDESERV', ''), + ('KDESERVTYPES', ''), ('KDEAPPS', ''), + ) + opts.Update(env) + + def getInstDirForResType(lenv,restype): + if len(restype) == 0 or not lenv.has_key(restype): + print RED+"unknown resource type "+restype+NORMAL + lenv.Exit(1) + else: + instdir = lenv[restype] + basedir=lenv['DESTDIR'] + ## support for installing into other folders when PREFIX is set - used by gnu stow + if basedir: instdir = instdir.replace(lenv['PREFIX'], basedir) + return instdir + + # reconfigure when things are missing + if not env['HELP'] and (env['_CONFIGURE'] or not env.has_key('QTDIR') or not env.has_key('KDEDIR')): + detect_kde(env) + opts.Save(cachefile, env) + + ## set default variables, one can override them in sconscript files + env.Append(CXXFLAGS = ['-I'+env['KDEINCLUDEPATH'], '-I'+env['QTINCLUDEPATH'] ], + LIBPATH = [env['KDELIBPATH'], env['QTLIBPATH'] ]) + + env['QT_AUTOSCAN'] = 1 + env['QT_DEBUG'] = 0 + + env['MEINPROC'] = 'meinproc' + env['MSGFMT'] = 'msgfmt' + + ## ui file processing + def uic_processing(target, source, env): + inc_kde ='#include \n#include \n' + inc_moc ='#include "%s"\n' % target[2].name + comp_h ='$QT_UIC -L $QTPLUGINS -nounload -o %s %s' % (target[0].path, source[0].path) + comp_c ='$QT_UIC -L $QTPLUGINS -nounload -tr tr2i18n -impl %s %s' % (target[0].path, source[0].path) + comp_moc ='$QT_MOC -o %s %s' % (target[2].path, target[0].path) + if env.Execute(comp_h): + return ret + dest = open( target[1].path, "w" ) + dest.write(inc_kde) + dest.close() + if env.Execute( comp_c+" >> "+target[1].path ): + return ret + dest = open( target[1].path, "a" ) + dest.write(inc_moc) + dest.close() + ret = env.Execute( comp_moc ) + return ret + 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) + target.append(bs+'.cpp') + target.append(bs+'.moc') + return target, source + env['BUILDERS']['Uic']=Builder(action=uic_processing,emitter=uicEmitter,suffix='.h',src_suffix='.ui') + + def kcfg_buildit(target, source, env): + comp='kconfig_compiler -d%s %s %s' % (str(source[0].get_dir()), source[1].path, source[0].path) + return env.Execute(comp) + def kcfg_stringit(target, source, env): + print "processing %s to get %s and %s" % (source[0].name, target[0].name, target[1].name) + 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) + # .h file is already there + target.append(bs+'.cpp') + + if not os.path.isfile(str(source[0])): + print RED+'kcfg file given '+str(source[0])+' does not exist !'+NORMAL + print os.popen('pwd').read() + 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 + if not kcfgfilename: + print 'invalid kcfgc file' + return 0 + source.append(str(source[0].get_dir())+'/'+kcfgfilename) + return target, source + + env['BUILDERS']['Kcfg']=Builder(action=env.Action(kcfg_buildit, kcfg_stringit), + emitter=kcfgEmitter, suffix='.h', src_suffix='.kcfgc') + + ## MOC processing + env['BUILDERS']['Moc']=Builder(action='$QT_MOC -o $TARGET $SOURCE',suffix='.moc',src_suffix='.h') + env['BUILDERS']['Moccpp']=Builder(action='$QT_MOC -o $TARGET $SOURCE',suffix='_moc.cpp',src_suffix='.h') + + ## KIDL file + env['BUILDERS']['Kidl']=Builder(action= 'dcopidl $SOURCE > $TARGET || (rm -f $TARGET ; false)', + suffix='.kidl', src_suffix='.h') + ## DCOP + env['BUILDERS']['Dcop']=Builder(action='dcopidl2cpp --c++-suffix cpp --no-signals --no-stub $SOURCE', + suffix='_skel.cpp', src_suffix='.kidl') + ## STUB + env['BUILDERS']['Stub']=Builder(action= 'dcopidl2cpp --c++-suffix cpp --no-signals --no-skel $SOURCE', + suffix='_stub.cpp', src_suffix='.kidl') + ## DOCUMENTATION + env['BUILDERS']['Meinproc']=Builder(action='$MEINPROC --check --cache $TARGET $SOURCE',suffix='.cache.bz2') + ## TRANSLATIONS + env['BUILDERS']['Transfiles']=Builder(action='$MSGFMT $SOURCE -o $TARGET',suffix='.gmo',src_suffix='.po') + + ## Handy helpers for building kde programs + ## You should not have to modify them .. + + ui_ext = [".ui"] + kcfg_ext = ['.kcfgc'] + header_ext = [".h", ".hxx", ".hpp", ".hh"] + cpp_ext = [".cpp", ".cxx", ".cc"] + skel_ext = [".skel", ".SKEL"] + stub_ext = [".stub", ".STUB"] + + def KDEfiles(lenv, target, source): + """ 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) """ + + q_object_search = re.compile(r'[^A-Za-z0-9]Q_OBJECT[^A-Za-z0-9]') + def scan_moc(bs, file_cpp): + addfile=None + # try to find the header + h_ext='' + for n_h_ext in header_ext: + if os.path.isfile(bs+n_h_ext): + h_ext=n_h_ext + break + # We have the header corresponding to the cpp file + if h_ext: + needscan=0 + # User asked for fastmoc, try to avoid scanning + if env.has_key('BKSYS_FASTMOC'): + if os.path.isfile(bs+'.moc'): + lenv.Moc(bs+h_ext) + elif os.path.isfile(bs+'_moc.cpp'): + lenv.Moccpp(bs+h_ext) + addfile=bs+'_moc.cpp' + else: + #print "need scanning "+os.getcwd()+'/'+bs+".moc" + needscan=1 + else: + needscan=1 + # We cannot avoid scanning the files ... + if needscan: + file_h=bs+h_ext + h_contents = open(file_h, 'rb').read() + if q_object_search.search(h_contents): + # we know now there is Q_OBJECT macro + lst = bs.split('/') + val = lst[ len(lst) - 1 ] + reg = '\n\s*#include\s*("|<)'+val+'.moc("|>)' + meta_object_search = re.compile(reg) + cpp_contents = open(file_cpp, 'rb').read() + if meta_object_search.search(cpp_contents): + lenv.Moc(file_h) + else: + lenv.Moccpp(file_h) + addfile=bs+'_moc.cpp' + print "WARNING: moc.cpp for "+bs+h_ext+" consider using #include instead" + return addfile + + src=[] + ui_files=[] + kcfg_files=[] + other_files=[] + kidl=[] + + source_=lenv.make_list(source) + + # 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: + if not bs in kidl: + kidl.append(bs) + lenv.Dcop(bs+'.kidl') + src.append(bs+'_skel.cpp') + elif ext in stub_ext: + if not bs in kidl: + kidl.append(bs) + lenv.Stub(bs+'.kidl') + src.append(bs+'_stub.cpp') + elif ext == ".moch": + lenv.Moccpp(bs+'.h') + src.append(bs+'_moc.cpp') + elif ext in cpp_ext: + src.append(file) + if not env.has_key('NOMOCFILE'): + ret = scan_moc(bs, file) + if ret: + src.append( ret ) + elif ext in ui_ext: + lenv.Uic(file) + src.append(bs+'.cpp') + elif ext in kcfg_ext: + lenv.Kcfg(file) + src.append(bs+'.cpp') + else: + src.append(file) + + for base in kidl: + lenv.Kidl(base+'.h') + + # Now check against typical 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" + return src + + + """ In the future, these functions will contain the code that will dump the + configuration for re-use from an IDE """ + import glob + def KDEinstall(lenv, restype, subdir, files): + if env.has_key('DUMPCONFIG'): + print "" % (restype, subdir) + for i in lenv.make_list(files): + print " " % i + print "" + return + + if not env['_INSTALL']: + return + dir = getInstDirForResType(lenv, restype) + install_list = lenv.bksys_install(dir+'/'+subdir, files, nodestdir=1) + return install_list + + def KDEinstallas(lenv, restype, destfile, file): + if not env['_INSTALL']: + return + dir = getInstDirForResType(lenv, restype) + install_list = lenv.InstallAs(dir+'/'+destfile, file) + env.Alias('install', install_list) + return install_list + + def KDEprogram(lenv, target, source, + includes='', localshlibs='', globallibs='', globalcxxflags=''): + """ Makes a kde program + The program is installed except if one sets env['NOAUTOINSTALL'] """ + src = KDEfiles(lenv, target, source) + program_list = lenv.Program(target, src) + + # we link the program against a shared library done locally, add the dependency + if not lenv.has_key('nosmart_includes'): + lenv.AppendUnique(CPPPATH=['./']) + if len(localshlibs)>0: + lst=lenv.make_list(localshlibs) + lenv.link_local_shlib(lst) + lenv.Depends( program_list, lst ) + + if len(includes)>0: + lenv.KDEaddpaths_includes(includes) + if len(globallibs)>0: + lenv.KDEaddlibs(globallibs) + if len(globalcxxflags)>0: + lenv.KDEaddflags_cxx(globalcxxflags) + + if not lenv.has_key('NOAUTOINSTALL'): + KDEinstall(lenv, 'KDEBIN', '', target) + return program_list + + def KDEshlib(lenv, target, source, kdelib=0, libprefix='lib', + includes='', localshlibs='', globallibs='', globalcxxflags='', vnum=''): + """ Makes a shared library for kde (.la file for klibloader) + The library is installed except if one sets env['NOAUTOINSTALL'] """ + src = KDEfiles(lenv, target, source) + + if not lenv.has_key('nosmart_includes'): + lenv.AppendUnique(CPPPATH=['./']) + # we link the program against a shared library done locally, add the dependency + lst=[] + if len(localshlibs)>0: + lst=lenv.make_list(localshlibs) + lenv.link_local_shlib(lst) + if len(includes)>0: + lenv.KDEaddpaths_includes(includes) + if len(globallibs)>0: + lenv.KDEaddlibs(globallibs) + if len(globalcxxflags)>0: + lenv.KDEaddflags_cxx(globalcxxflags) + + restype = 'KDEMODULE' + if kdelib==1: + restype = 'KDELIB' + + library_list = lenv.bksys_shlib(target, src, getInstDirForResType(lenv, restype), libprefix, vnum, nodestdir=1) + if len(lst)>0: lenv.Depends( library_list, lst ) + + return library_list + + def KDEstaticlib(lenv, target, source): + """ 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 limitations + (cannot handle sources in separate folders - takes extra processing) """ + if not lenv.has_key('nosmart_includes'): + lenv.AppendUnique(CPPPATH=['./']) + src = KDEfiles(lenv, target, source) + return lenv.StaticLibrary(target, src) + # do not install static libraries by default + + def KDEaddflags_cxx(lenv, fl): + """ Compilation flags for C++ programs """ + lenv.AppendUnique(CXXFLAGS = lenv.make_list(fl)) + + def KDEaddflags_c(lenv, fl): + """ Compilation flags for C programs """ + lenv.AppendUnique(CFLAGS = lenv.make_list(fl)) + + def KDEaddflags_link(lenv, fl): + """ Add link flags - Use this if KDEaddlibs below is not enough """ + lenv.PrependUnique(LINKFLAGS = lenv.make_list(fl)) + + def KDEaddlibs(lenv, libs): + """ Helper function """ + lenv.AppendUnique(LIBS = lenv.make_list(libs)) + + def KDEaddpaths_includes(lenv, paths): + """ Add new include paths """ + lenv.AppendUnique(CPPPATH = lenv.make_list(paths)) + + def KDEaddpaths_libs(lenv, paths): + """ Add paths to libraries """ + lenv.PrependUnique(LIBPATH = lenv.make_list(paths)) + + def KDElang(lenv, folder, appname): + """ Process translations (.po files) in a po/ dir """ + transfiles = glob.glob(folder+'/*.po') + for lang in transfiles: + result = lenv.Transfiles(lang) + country = SCons.Util.splitext(result[0].name)[0] + KDEinstallas(lenv, 'KDELOCALE', country+'/LC_MESSAGES/'+appname+'.mo', result) + + def KDEicon(lenv, icname='*', path='./', restype='KDEICONS', subdir=''): + """Contributed by: "Andrey Golovizin" + modified by "Martin Ellis" + + Installs icons with filenames such as cr22-action-frame.png into + KDE icon hierachy with names like icons/crystalsvg/22x22/actions/frame.png. + + Global KDE icons can be installed simply using env.KDEicon('name'). + The second parameter, path, is optional, and specifies the icons + location in the source, relative to the SConscript file. + + To install icons that need to go under an applications directory (to + avoid name conflicts, for example), use e.g. + env.KDEicon('name', './', 'KDEDATA', 'appname/icons')""" + + if env.has_key('DUMPCONFIG'): + print "" % (path+subdir) + return + + type_dic = { 'action' : 'actions', 'app' : 'apps', 'device' : + 'devices', 'filesys' : 'filesystems', 'mime' : 'mimetypes' } + dir_dic = { + 'los' :'locolor/16x16', + 'lom' :'locolor/32x32', + 'him' :'hicolor/32x32', + 'hil' :'hicolor/48x48', + 'lo16' :'locolor/16x16', + 'lo22' :'locolor/22x22', + 'lo32' :'locolor/32x32', + 'hi16' :'hicolor/16x16', + 'hi22' :'hicolor/22x22', + 'hi32' :'hicolor/32x32', + 'hi48' :'hicolor/48x48', + 'hi64' :'hicolor/64x64', + 'hi128':'hicolor/128x128', + 'hisc' :'hicolor/scalable', + 'cr16' :'crystalsvg/16x16', + 'cr22' :'crystalsvg/22x22', + 'cr32' :'crystalsvg/32x32', + 'cr48' :'crystalsvg/48x48', + 'cr64' :'crystalsvg/64x64', + 'cr128':'crystalsvg/128x128', + 'crsc' :'crystalsvg/scalable' + } + + iconfiles = [] + for ext in "png xpm mng svg svgz".split(): + files = glob.glob(path+'/'+'*-*-%s.%s' % (icname, ext)) + iconfiles += files + for iconfile in iconfiles: + lst = iconfile.split('/') + filename = lst[ len(lst) - 1 ] + tmp = filename.split('-') + if len(tmp)!=3: + print RED+'WARNING: icon filename has unknown format: '+iconfile+NORMAL + continue + [icon_dir, icon_type, icon_filename]=tmp + try: + basedir=getInstDirForResType(lenv, restype) + destfile = '%s/%s/%s/%s/%s' % (basedir, subdir, dir_dic[icon_dir], type_dic[icon_type], icon_filename) + except KeyError: + print RED+'WARNING: unknown icon type: '+iconfile+NORMAL + continue + ## Do not use KDEinstallas here, as parsing from an ide will be necessary + if env['_INSTALL']: + env.Alias('install', env.InstallAs( destfile, iconfile ) ) + + ## This function uses env imported above + def docfolder(lenv, folder, lang, destination=""): + # folder is the folder to process + # lang is the language + # destination is the subdirectory in KDEDOC + docfiles = glob.glob(folder+"/???*.*") # file files that are at least 4 chars wide :) + # warn about errors + #if len(lang) != 2: + # print "error, lang must be a two-letter string, like 'en'" + + # when the destination is not given, use the folder + if len(destination) == 0: + destination=folder + docbook_list = [] + for file in docfiles: + # do not process folders + if not os.path.isfile(file): + continue + # do not process the cache file + if file == 'index.cache.bz2': + continue + # ignore invalid files (TODO??) + if len( SCons.Util.splitext( file ) ) <= 1 : + continue + + ext = SCons.Util.splitext( file )[1] + + # docbook files are processed by meinproc + if ext != '.docbook': + continue + docbook_list.append( file ) + lenv.KDEinstall('KDEDOC', lang+'/'+destination, file) + # Now process the index.docbook files .. + if len(docbook_list) == 0: + return + if not os.path.isfile( folder+'/index.docbook' ): + print "Error, index.docbook was not found in "+folder+'/index.docbook' + return + ## Define this to 1 if you are writing documentation else to 0 :) + if env.has_key('i_am_a_documentation_writer'): + for file in docbook_list: + lenv.Depends( folder+'index.cache.bz2', file ) + lenv.Meinproc( folder+'/index.cache.bz2', folder+'/index.docbook' ) + lenv.KDEinstall( 'KDEDOC', lang+'/'+destination, folder+'/index.cache.bz2' ) + + #valid_targets = "program shlib kioslave staticlib".split() + import generic + class kobject(generic.genobj): + def __init__(self, val, senv=None): + if senv: generic.genobj.__init__(self, val, senv) + else: generic.genobj.__init__(self, val, env) + self.iskdelib=0 + def it_is_a_kdelib(self): self.iskdelib=1 + def execute(self): + self.lockchdir() + if self.orenv.has_key('DUMPCONFIG'): + print self.xml() + return + if (self.type=='shlib' or self.type=='kioslave'): + install_dir = 'KDEMODULE' + if self.iskdelib==1: install_dir = 'KDELIB' + self.instdir=getInstDirForResType(self.orenv, install_dir) + self.nodestdir=1 + elif self.type=='program': + self.instdir=getInstDirForResType(self.orenv, 'KDEBIN') + self.nodestdir=1 + + self.src=KDEfiles(env, self.target, self.source) + generic.genobj.execute(self) + self.unlockchdir() + + def xml(self): + ret= '\n' % (self.type, self.chdir, self.target, self.cxxflags, self.cflags, self.includes, self.linkflags, self.libpaths, self.libs, self.vnum, self.iskdelib, self.libprefix) + if self.source: + for i in self.orenv.make_list(self.source): + ret += ' \n' % i + ret += "" + return ret + + # Attach the functions to the environment so that SConscripts can use them + SConsEnvironment.KDEprogram = KDEprogram + SConsEnvironment.KDEshlib = KDEshlib + SConsEnvironment.KDEstaticlib = KDEstaticlib + SConsEnvironment.KDEinstall = KDEinstall + SConsEnvironment.KDEinstallas = KDEinstallas + SConsEnvironment.KDElang = KDElang + SConsEnvironment.KDEicon = KDEicon + + SConsEnvironment.KDEaddflags_cxx = KDEaddflags_cxx + SConsEnvironment.KDEaddflags_c = KDEaddflags_c + SConsEnvironment.KDEaddflags_link = KDEaddflags_link + SConsEnvironment.KDEaddlibs = KDEaddlibs + SConsEnvironment.KDEaddpaths_includes = KDEaddpaths_includes + SConsEnvironment.KDEaddpaths_libs = KDEaddpaths_libs + + SConsEnvironment.docfolder = docfolder + SConsEnvironment.kobject = kobject + diff --git a/asciiquarium/admin/scons-mini.tar.bz2 b/asciiquarium/admin/scons-mini.tar.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..53d41bba9518729a058f82f6b17752215802bd69 GIT binary patch literal 58132 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 V`21(HFbseH7ji{7P>?dhCBfod#RvcZ literal 0 HcmV?d00001 diff --git a/asciiquarium/configure b/asciiquarium/configure new file mode 100755 index 00000000..c5a93dc3 --- /dev/null +++ b/asciiquarium/configure @@ -0,0 +1,164 @@ +#!/usr/bin/env python +# Configure script for abakus. I think it's better than the sample that came +# with bksys. Feel free to do with it what you want. +# By Michael Pyne + +import sys +import re + +BOLD="\033[1m" +RED="\033[91m" +GREEN="\033[92m" +YELLOW="\033[93m" +CYAN="\033[96m" +NORMAL="\033[0m" + +PROGRAM_NAME='aa.kss' + +# First let's see if they asked for help. +if '--help' in sys.argv: + print '''This is a configure script to prepare %s for building. + +You can pass the command line argument debug=1 to enable debugging for the +application, which can be useful if you have problems. + +Otherwise, just run ./configure, and if you don't already have scons, a mini +version will be installed suitable for building %s. +'''.replace('%s', PROGRAM_NAME) + sys.exit(0) + +# Check that we have the minimum version of Python needs to run SCons. +if sys.hexversion < 0x02030000: + # Use regexp for compatibility with ancient Python + version = re.split(' ', sys.version)[0] + + print RED + 'Sorry, your version of Python is too old.' + NORMAL + print PROGRAM_NAME + ' requires Python 2.3 or greater to build.' + print "\nYou have Python " + version + sys.exit(1) + +import os + +# Check if scons is installed. We can use cool Python features now that we +# know we aren't using an ancient version of Python. +result = os.system('scons -v > /dev/null 2>&1') +scons = 'scons' + +if os.WEXITSTATUS(result) == 0: + print GREEN + "scons already installed." + NORMAL +else: + # If we didn't find scons, don't whine to the user about it, just fix it. + print YELLOW + 'scons not installed, installing local copy.' + NORMAL + + # Split this into two steps since some tars don't use j to mean bzip2 + # compressed. + result = os.system('bzcat admin/scons-mini.tar.bz2 | tar x') + + if os.WEXITSTATUS(result) != 0: + print RED + 'Unable to extract scons' + NORMAL + sys.exit(2) + + scons = '%s/scons' % os.getcwd() + +# Now we now where scons is. Let's create a Makefile (after we configure) so +# that all the user has to do is type 'make'. Allow the user to pass command +# line arguments, which will be passed to the configure process. +if len(sys.argv) < 2: + options = '' +else: + options = " ".join(sys.argv[1:]) + # reduce is pretty cool +# options = reduce(lambda x, y: x + '\n' + y, sys.argv[1:]) + +result = os.system(scons + ' configure ' + options) +if os.WEXITSTATUS(result) != 0: + print RED + 'Unable to configure scons' + NORMAL + sys.exit(3) + +# Recursive generates a makefile for a directory. If topDir is True, the +# Makefile is slightly different. +def generate_makefile(dir, topDir = False): + + file = name + "/Makefile" + + # Write out Makefile. + try: + makefile = open(file, 'w') + except IOError: + print RED + "Unable to open " + file + NORMAL + sys.exit(4) + + text = ''' +## Makefile automatically generated by configure + +SCONS=$scons + +# $scons : compile +# $scons -c : clean +# $scons install : install +# $scons -c install : uninstall and clean + +# Default target: use scons to build the program +all: + @$(SCONS) -Q + +# Debugging possibilies: +# $scons --debug=explain, $scons --debug=tree + +# To optimize the runtime: +# $scons --max-drift=1 --implicit-deps-unchanged +debug: + @$(SCONS) -Q --debug=tree + +clean: + @$(SCONS) -c + +install: + @$(SCONS) install + +uninstall: + @$(SCONS) uninstall + +# This target creates a tarball of the project (in theory) +dist: + @$(SCONS) dist +''' + + if topDir: + text = text.replace('$scons', scons) + else: + text = text.replace('$scons', scons + ' -u') + + try: + print "Generating " + GREEN + file + NORMAL + makefile.write(text) + makefile.close() + except IOError: + print RED + "Unable to write to the Makefile!" + NORMAL + sys.exit(5) + +# Recursively generate Makefiles for convienience. +for name, dirs, files in os.walk('.'): + # Don't try to build hidden directories. + remove = filter(lambda x: x[0] == '.', dirs) + for i in remove: + dirs.remove(i) + + if 'SConstruct' in files: + # We're in the very top directory. + generate_makefile(name, topDir = True) + + for dir in ['cache', 'admin']: + if dir in dirs: + dirs.remove(dir) + elif 'SConscript' in files: + generate_makefile(name) + +# The Makefile has been written, we're pretty much done. +message = ''' +The Makefile(s) have been generated. Type: + `make' to build %s, and + `make install' to install %s. +'''.replace('%s', PROGRAM_NAME) + +print GREEN + message + NORMAL diff --git a/asciiquarium/src/AASaverConfig.kcfgc b/asciiquarium/src/AASaverConfig.kcfgc new file mode 100644 index 00000000..f709650b --- /dev/null +++ b/asciiquarium/src/AASaverConfig.kcfgc @@ -0,0 +1,4 @@ +File=asciiquarium.kcfg +ClassName=AASaverConfig +Singleton=true +Mutators=true diff --git a/asciiquarium/src/Doxyfile b/asciiquarium/src/Doxyfile new file mode 100644 index 00000000..9588f8b2 --- /dev/null +++ b/asciiquarium/src/Doxyfile @@ -0,0 +1,1218 @@ +# Doxyfile 1.4.2 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = Asciiquarium + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = 0.3.2 + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = doc + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, +# Dutch, Finnish, French, German, Greek, Hungarian, Italian, Japanese, +# Japanese-en (Japanese with English messages), Korean, Korean-en, Norwegian, +# Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, +# Swedish, and Ukrainian. + +OUTPUT_LANGUAGE = English + +# This tag can be used to specify the encoding used in the generated output. +# The encoding is not always determined by the language that is chosen, +# but also whether or not the output is meant for Windows or non-Windows users. +# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES +# forces the Windows encoding (this is the default for the Windows binary), +# whereas setting the tag to NO uses a Unix-style encoding (the default for +# all platforms other than Windows). + +USE_WINDOWS_ENCODING = NO + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = YES + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = YES + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like the Qt-style comments (thus requiring an +# explicit @brief command for a brief description. + +JAVADOC_AUTOBRIEF = YES + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the DETAILS_AT_TOP tag is set to YES then Doxygen +# will output the detailed description near the top, like JavaDoc. +# If set to NO, the detailed description appears after the member +# documentation. + +DETAILS_AT_TOP = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java sources +# only. Doxygen will then generate output that is more tailored for Java. +# For instance, namespaces will be presented as packages, qualified scopes +# will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. + +SHOW_DIRECTORIES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from the +# version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the progam writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. + +EXCLUDE_PATTERNS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES (the default) +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES (the default) +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = doxygen-stylesheet.css + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be +# generated containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. + +GENERATE_TREEVIEW = YES + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = YES + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = YES + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = letter + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_PREDEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will +# generate a call dependency graph for every global function or class method. +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_WIDTH = 1024 + +# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_HEIGHT = 1024 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that a graph may be further truncated if the graph's +# image dimensions are not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH +# and MAX_DOT_GRAPH_HEIGHT). If 0 is used for the depth value (the default), +# the graph is not depth-constrained. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, which results in a white background. +# Warning: Depending on the platform used, enabling this option may lead to +# badly anti-aliased labels on the edges of a graph (i.e. they become hard to +# read). + +DOT_TRANSPARENT = YES + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = YES + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO diff --git a/asciiquarium/src/SConscript b/asciiquarium/src/SConscript new file mode 100644 index 00000000..ef7ffeb3 --- /dev/null +++ b/asciiquarium/src/SConscript @@ -0,0 +1,44 @@ +#! /usr/bin/env python +## This script demonstrates how to build and install +## a simple kde program having KconfigXT settings +## with scons +## +## Thomas Nagy, 2004, 2005 + +## This file can be reused freely for any project (see COPYING) + +############################ +## load the config + +## Use the environment and the tools set in the top-level +## SConstruct file (set with 'Export') - this is very important + +Import( 'env' ) +myenv=env.Copy() + +############################# +## the programs to build + +# The sources for our program - only .ui, .skel and .cpp are accepted +aa_sources = """ +aasaver.cpp +screen.cpp +frame.cpp +sprite.cpp +AASaverConfig.kcfgc +settingswidget.ui +""" + +myenv.KDEprogram( "asciiquarium.kss", aa_sources ) + +############################ +## Customization + +## Additional include paths for compiling the source files +## Always add '../' (top-level directory) because moc makes code that needs it +myenv.KDEaddpaths_includes('#/src/ #/') + +## Necessary libraries to link against +myenv.KDEaddlibs( 'qt-mt kdecore kdeui kscreensaver' ) +myenv.KDEinstall('KDEAPPS', 'System/ScreenSavers', 'asciiquarium.desktop') +myenv.KDEinstall('KDEKCFG', '', 'asciiquarium.kcfg') diff --git a/asciiquarium/src/aasaver.cpp b/asciiquarium/src/aasaver.cpp new file mode 100644 index 00000000..a85c7d19 --- /dev/null +++ b/asciiquarium/src/aasaver.cpp @@ -0,0 +1,1256 @@ +/* + * Asciiquarium - Native KDE Screensaver based on the Asciiquarium program + * (c) Kirk Baucom , which you can find at + * http://www.robobunny.com/projects/asciiquarium/ + * + * Ported to KDE by Maksim Orlovich and + * Michael Pyne . + * + * Copyright (c) 2003 Kirk Baucom + * Copyright (c) 2005 Maksim Orlovich + * Copyright (c) 2005 Michael Pyne + * + * ASCII Art was mostly created by Joan Stark: + * http://www.geocities.com/SoHo/7373/ + * + * the rest was crafted by Kirk Baucom, and all of it was edited by + * Maksim Orlovich and Michael Pyne to adopt to C++ formatting. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +//Stub stolen from: +// klines 0.1.1 - Basic screen saver for KDE +// by Dirk Staneker 1997 + +#include +#include +#include + +#include +#include +#include + +#include "screen.h" +#include "frame.h" +#include "sprite.h" +#include "aasaver.h" + +#include "AASaverConfig.h" +#include "settingswidget.h" + +#define ARRAY_SIZE(arr) sizeof(arr)/sizeof(arr[0]) + +AASaver::AASaver( WId id ): KScreenSaver(id) +{ + screen = new Screen(this); + addEnvironment(); + addCastle(); + addAllSeaweed(); + addAllFish(); + addRandom(screen); + + setBackgroundMode(NoBackground); + setWFlags(WNoAutoErase); + update(rect()); +} + + +QString AASaver::randColor(QString color_mask) +{ + char colors[] = {'c','C','r','R','y','Y','b','B','g','G','m','M'}; + for (int i = 1; i <= 9; ++i) + { + char color = colors[intRand(ARRAY_SIZE(colors))]; + color_mask.replace('0' + i, color); + } + return color_mask; +} + +void AASaver::addCastle() +{ + QString castle_image = + " T~~\n" + " |\n" + " /^\\\n" + " / \\\n" + " _ _ _ / \\ _ _ _\n" + "[ ]_[ ]_[ ]/ _ _ \\[ ]_[ ]_[ ]\n" + "|_=__-_ =_|_[ ]_[ ]_|_=-___-__|\n" + " | _- = | =_ = _ |= _= |\n" + " |= -[] |- = _ = |_-=_[] |\n" + " | =_ |= - ___ | =_ = |\n" + " |= []- |- /| |\\ |=_ =[] |\n" + " |- =_ | =| | | | |- = - |\n" + " |_______|__|_|_|_|__|_______|\n"; + + + QString castle_mask = + " RR\n" + "\n" + " yyy\n" + " y y\n" + " y y\n" + " y y\n" + "\n" + "\n" + "\n" + " yyy\n" + " yy yy\n" + " y y y y\n" + " yyyyyyy\n"; + + Frame f(castle_image, castle_mask, 0x686868/* XXX: why grey? */ ); + + Sprite* castle = new Sprite(screen, + screen->width() - 32, screen->height() - 13, 22); + castle->addFrame(f); + screen->addSprite(castle); +} + +void AASaver::addEnvironment() +{ + QString water_line_segment[] = { + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~", + "^^^^ ^^^ ^^^ ^^^ ^^^^ ", + "^^^^ ^^^^ ^^^ ^^ ", + "^^ ^^^^ ^^^ ^^^^^^ " + }; + + // tile the segments so they stretch across the screen + int segment_size = water_line_segment[0].length(); + int segment_repeat = int(screen->width()/segment_size) + 1; + + for (unsigned i = 0; i < ARRAY_SIZE(water_line_segment); ++i) { + //do the tiling + QString out; + for (int r = 0; r < segment_repeat; ++r) + out += water_line_segment[i]; + + //create a sprite. + Sprite* s = new Sprite(screen, 0, i + 5, 22); + s->addFrame(Frame(out, QString::null, 0x149494)); + screen->addSprite(s); + } +} + +void AASaver::addAllSeaweed() +{ + // figure out how many seaweed to add by the width of the screen + int seaweed_count = int(screen->width() / 15.0); + for (int i = 1; i <= seaweed_count; ++i) + addSeaweed(screen); +} + +/** + * Special class to represent seaweed. Seaweed can disappear over time, and + * when this happens, this class will automatically spawn another one. + */ +class Seaweed: public Sprite +{ + int m_ticks; ///< Number of animation ticks elapsed. + int m_lifeTimeMS; ///< Life time of seaweed in milliseconds. + +public: + /** + * Constructor. The \p x, \p y, and \p z coordinates are all in logical + * coordinates. + * + * @param s The Screen to be created in. + * @param x The x coordinate to place the seaweed at. + * @param y The y coordinate to place the seaweed at. + * @param life The length of time in milliseconds the seaweed will live for. + */ + Seaweed(Screen* s, int x, int y, int life): Sprite(s, x, y, 21), + m_ticks(0), m_lifeTimeMS(life) + { + } + + /** + * Reimplemented from Sprite::tickUpdate() to handle keeping track of + * Seaweed lifetime. Calls the inherited tickUpdate() as well. + */ + virtual bool tickUpdate() + { + ++m_ticks; + if (m_ticks * m_screen->msPerTick() > m_lifeTimeMS) + { + kill(); + AASaver::addSeaweed(m_screen); + } + + return Sprite::tickUpdate(); + } +}; + +void AASaver::addSeaweed(Screen* screen) +{ + QString seaweed_image[] = {"", ""}; + int height = intRand(5) + 3; + for (int i = 1; i <= height; ++i) + { + int left_side = i % 2; + int right_side = !left_side; + seaweed_image[left_side] += "(\n"; + seaweed_image[right_side] += " )\n"; + } + + int x = intRand(screen->width() - 2) + 1; + int y = screen->height() - height; + + Seaweed* s = new Seaweed(screen, x, y, + intRand(4*60000) + (8*60000)); // seaweed lives for 8 to 12 minutes + s->addFrame(Frame(seaweed_image[0], QString::null, 0x18AF18)); + s->addFrame(Frame(seaweed_image[1], QString::null, 0x18AF18)); + s->setFrameDelay(intRand(50) + 250); + screen->addSprite(s); +} + +/** + * Class to represent an AirBubble. The bubble will automatically float up, + * even though it is not descended from MovingSprite. + */ +class AirBubble : public Sprite +{ + const int m_startY; ///< Y coordinate we started at, needed to choose a frame. + +public: + /** + * Constructor. The \p x, \p y, and \p z coordinates are all in logical + * coordinates. + * + * @param screen The Screen to be created in. + * @param x The x coordinate to start at. + * @param y The y coordinate to start at. + * @param z The depth to start at. + */ + AirBubble(Screen *screen, int x, int y, int z) : + Sprite(screen, x, y, z), m_startY(y) + { + addFrame(Frame(".", QString(), 0x18B2B2)); + addFrame(Frame("o", QString(), 0x18B2B2)); + addFrame(Frame("O", QString(), 0x18B2B2)); + + setFrameDelay(100); + } + + /** + * Reimplemented from Sprite::tickUpdate() to handle moving the sprite and + * updating the current frame. The inherited tickUpdate() is not called. + */ + virtual bool tickUpdate() + { + if (!timerTick()) + return false; + + erase(); + + m_currentFrame = 0; + if(m_startY - m_y > 5) + m_currentFrame = 1; + if(m_startY - m_y > 11) + m_currentFrame = 2; + + m_y--; + if(m_y < 9) + kill(); + + return true; + } +}; + +/** + * Moving sprite, will be killed when it moves off of the screen. + */ +class MovingSprite: public Sprite +{ +protected: + int m_direct; ///< Direction to move in, -1 == left, 1 == right. + double m_speed; ///< Speed to move at (Currently m_speed per tick). + double m_realX; ///< Used for accuracy, holds fractional x position. + int m_ticksSinceLastChange; ///< Number of timer ticks since last frame change. + int m_frameTime; ///< Amount of time in milliseconds to show each frame. + +public: + /** + * Constructor. The \p x, \p y, and \p z coordinates are all in logical + * coordinates. + * + * @param screen The Screen to be created in. + * @param direct The direction to move the sprite in along the X axis, either + * -1 for the left direction, or 1 for the right direction. + * @param speed The speed to move the sprite in along the X axis, in + * character cells per tick. Use Screen::msPerTick() to find + * out how long a tick takes. The speed can be fractional + * (e.g. 1.5 cells per tick). + * @param x The x coordinate to start at. + * @param y The y coordinate to start at. + * @param z The depth to start at. + */ + MovingSprite(Screen* screen, int direct, double speed, int x, int y, int z): + Sprite(screen, x, y, z), m_direct(direct), m_speed(speed), m_realX(x), + m_ticksSinceLastChange(0), m_frameTime(250) + { + } + + /** + * Sets the amount of time a frame is shown. Use this function for + * MovingSprites that are also animated. + * + * @param milliseconds Amount of time to show a frame for in milliseconds. + */ + void setFrameTime(int milliseconds) + { + m_frameTime = milliseconds; + } + + /// Returns the amount of time a frame lasts in milliseconds. + int frameTime() const { return m_frameTime; } + + /// Returns the direction the sprite travels in. + int direction() const + { + return m_direct; + } + + /// Returns the fractional speed of the sprite. + double realSpeed() const + { + return m_speed; + } + + /// Returns the real (fractional) X position of the sprite. + double realX() const + { + return m_realX; + } + + /** + * Reimplemented from Sprite::tickUpdate() to handle motion and frame + * animation. This function will automatically kill() this sprite when + * it moves off screen. The inherited tickUpdate() is not called. + */ + virtual bool tickUpdate() + { + if (!timerTick()) + return false; + + erase(); + m_realX += (m_direct * m_speed); + m_x = (int) m_realX; + + ++m_ticksSinceLastChange; + if(m_ticksSinceLastChange * m_screen->msPerTick() > m_frameTime) + { + m_ticksSinceLastChange = 0; + + ++m_currentFrame; + if(m_currentFrame == m_frames.size()) + m_currentFrame = 0; + } + + if((m_x + m_frames[m_currentFrame].width() < 0) || (m_x > m_screen->width())) + kill(); + + return true; + } +}; + +/** + * Will spawn a random sprite when killed, otherwise behaves just like + * MovingSprite. + */ +class RandomMovingSprite : public MovingSprite +{ +public: + RandomMovingSprite(Screen *screen, int direct, double speed, int x, int y, int z): + MovingSprite(screen, direct, speed, x, y, z) + { + } + + /// Spawns another RandomMovingSprite before dying. + virtual void kill() + { + MovingSprite::kill(); + AASaver::addRandom(m_screen); + } +}; + +/** + * Special subclass that represents a fish. Used so TeethSprite knows when it + * has caused a collision, and also to handle air bubble generation. + */ +class FishSprite : public MovingSprite +{ + double m_spacesPerBubble; ///< Amount of spaces a fish moves for each bubble. + double m_lastBubbleRelease; ///< Amount of space traveled since the last bubble. + +public: + FishSprite(Screen* screen, int direct, double speed, int x, int y, int z): + MovingSprite(screen, direct, speed, x, y, z), m_lastBubbleRelease(x) + { + m_spacesPerBubble = AASaver::doubleRand(screen->width()) + 12.0; + } + + /// Spawns another fish before dying. + virtual void kill() + { + MovingSprite::kill(); + AASaver::addFish(m_screen); + } + + /** + * Reimplemented from MovingSprite::tickUpdate() to handle creating air + * bubbles. Inherited tickUpdate() is still called. + */ + virtual bool tickUpdate() + { + if(!MovingSprite::tickUpdate()) + return false; + + if(isKilled()) + return true; + + if(QABS(realX() - m_lastBubbleRelease) >= m_spacesPerBubble) + { + m_lastBubbleRelease = realX(); + + int bubbleX = m_x; + QRect geometry = geom(); + + if(m_direct > 0) // Moving right + bubbleX += geometry.width(); + + AASaver::addBubble(m_screen, bubbleX, m_y + geometry.height() / 2 - 1, m_z - 1); + } + + return true; + } +}; + +void AASaver::addAllFish() +{ + // Determine how many logical pixels we are dealing with, and find out how + // many we'd be dealing with in full screen, and then scale the user's + // number down to adjust so that we look about the same in a window as we + // do fullscreen. TODO: Xinerama issues? + QRect fullScreenGeometry = kapp->desktop()->screenGeometry(); + + int full_width = fullScreenGeometry.width() / screen->cellWidth(); + int full_height = fullScreenGeometry.height() / screen->cellHeight() - 9; + int full_size = full_width * full_height; + int screen_size = (screen->height() - 9) * screen->width(); + + int fish_count = AASaverConfig::fishCount() * screen_size / full_size; + if(fish_count < 5) + fish_count = 5; + + for (int i = 1; i <= fish_count; ++ i) + addFish(screen); +} + +Sprite *AASaver::newFish(Screen *screen) +{ + QString fish_image[] = { +" \\\n" +" ...\\..,\n" +"\\" "??" "/' \\\n" // trigraphs suck +" >= ( ' >\n" +"/??\\ / /\n" +" `\"'\"'/''\n", + +" 2\n" +" 1112111\n" +"6 11 1\n" +" 66 7 4 5\n" +"6 1 3 1\n" +" 11111311\n", + +////////////////////////////// +" /\n" +" ,../...\n" +" / '\\" "??" "/\n" // trigraphs suck +"< ' ) =<\n" +" \\ \\ /??\\\n" +" `'\\'\"'\"'\n", + +" 2\n" +" 1112111\n" +" 1 11 6\n" +"5 4 7 66\n" +" 1 3 1 6\n" +" 11311111\n", +////////////////////////////// +" \\\n" +"\\?/--\\\n" +">= (o>\n" +"/?\\__/\n" +" /\n", + +" 2\n" +"6 1111\n" +"66 745\n" +"6 1111\n" +" 3\n", + +////////////////////////////// +" /\n" +" /--\\?/\n" +"::::::::;;\\\\\\\n" +" ''\\\\\\\\\\''?';\\\n", + +" 222\n" +" 1122211 666\n" +" 4111111111666\n" +"51111111111666\n" +" 113333311 666\n", + +////////////////////////////// +" __\n" +"><_'>\n" +" '\n", + +" 11\n" +"61145\n" +" 3\n", + +////////////////////////////// +" __\n" +"<'_><\n" +" `\n", + +" 11\n" +"54116\n" +" 3\n", + +////////////////////////////// +" ..\\,\n" +">=' ('>\n" +" '''/''\n", + +" 1121\n" +"661 745\n" +" 111311\n", + +////////////////////////////// +" ,/..\n" +"<') `=<\n" +" ``\\```\n", + +" 1211\n" +"547 166\n" +" 113111\n", + +////////////////////////////// +" \\\n" +" / \\\n" +">=_('>\n" +" \\_/\n" +" /\n", + +" 2\n" +" 1 1\n" +"661745\n" +" 111\n" +" 3\n", + +////////////////////////////// +" /\n" +" / \\\n" +"<')_=<\n" +" \\_/\n" +" \\\n", + +" 2\n" +" 1 1\n" +"547166\n" +" 111\n" +" 3\n", + +////////////////////////////// +" ,\\\n" +">=('>\n" +" '/\n", + +" 12\n" +"66745\n" +" 13\n", + +////////////////////////////// +" /,\n" +"<')=<\n" +" \\`\n", + +" 21\n" +"54766\n" +" 31\n", + +////////////////////////////// +" __\n" +"\\/ o\\\n" +"/\\__/\n", + +" 11\n" +"61 41\n" +"61111\n", + +////////////////////////////// +" __\n" +"/o \\/\n" +"\\__/\\\n", + +" 11\n" +"14 16\n" +"11116\n" +}; + + // # 1: body + // # 2: dorsal fin + // # 3: flippers + // # 4: eye + // # 5: mouth + // # 6: tailfin + // # 7: gills* + int fish_num = intRand(ARRAY_SIZE(fish_image)/2); + int fish_index = fish_num * 2; + + double speed = doubleRand(2) + 0.25; + int depth = 3 + intRand(18); + + QString color_mask = fish_image[fish_index+1]; + color_mask.replace('4', 'W'); + + color_mask = randColor(color_mask); + + Frame fishFrame(fish_image[fish_index], color_mask, 0); + int max_height = 9; + int min_height = screen->height() - fishFrame.height(); + + int x, y, dir; + y = max_height + intRand(min_height - max_height); + if (fish_num % 2) + { + x = screen->width() - 2; + dir = -1; + } + else + { + x = 1 - fishFrame.width(); + dir = 1; + } + + Sprite* fish = new FishSprite(screen, dir, speed, x, y, depth); + fish->addFrame(fishFrame); + + return fish; +} + +void AASaver::addFish(Screen* screen) +{ + screen->addSprite(newFish(screen)); +} + +/** + * Sprite that represents a blood "splat" in the water. + */ +class Splat : public Sprite +{ +public: + /** + * Constructor. + * + * @param screen The Screen to create the splat in. + * @param center The point to center the splat around. + * @param depth The depth to create the splat at. + */ + Splat(Screen *screen, QPoint center, int depth) : + Sprite(screen, 0, 0, depth, 450 /* frame Delay */) + { + QString splats[] = { +"\n" +" .\n" +" ***\n" +" '\n" +"" +, + +"\n" +" \",*;`\n" +" \"*,**\n" +" *\"'~'\n" +"" +, +" , ,\n" +" \" \",\"'\n" +" *\" *'\"\n" +" \" ; .\n" +"" +, +"* ' , ' `\n" +"' ` * . '\n" +" ' `' \",'\n" +"* ' \" * .\n" +"\" * ', '" + }; + + for(unsigned i = 0; i < ARRAY_SIZE(splats); ++i) + addFrame(Frame(splats[i], QString(), 0xB21818, ' ')); + + QRect r(center, QSize(9, 5)); + r.moveCenter(center); + m_x = r.x(); + m_y = r.y(); + + setDieAfterLastFrame(true); + } +}; + +/** + * Invisible sprite which are created on a shark's teeth, to handle collisions + * with fish, creating splats and kill()'ing the fish. + */ +class TeethSprite : public MovingSprite +{ +public: + /** + * Constructor. Copied parameters as appropriate from \p shark. + * + * @param shark The shark to create the teeth over. + */ + TeethSprite(MovingSprite *shark) : MovingSprite(shark->screen(), shark->direction(), + shark->realSpeed(), 2 + shark->geom().left(), shark->geom().top(), shark->depth()) + { + m_y += 7; + m_z -= 1; + m_realX = 2 + shark->realX(); + + if(m_direct > 0) // Moving to right. + m_realX = -10; + + addFrame(Frame("????????", QString(), 0)); + } + + /// Returns true since we can collide. + bool canCollide() const { return true; } + + /** + * Reimplemented in order to handle collisions. When colliding with a + * FishSprite, the fish is kill()'ed and a splat is created in its place. + * Otherwise, nothing is done. + * + * @param sprite The Sprite we collided with. + */ + void collision(Sprite *sprite) + { + if(dynamic_cast(sprite)) { + kdDebug() << "A fish just got killinated!\n"; + + sprite->erase(); + sprite->kill(); + + screen()->addSprite(new Splat(screen(), sprite->geom().center(), depth() - 1)); + } + } +}; + +void AASaver::addShark(Screen* screen) +{ + QString shark_image[] = { +" __\n" +" ( `\\\n" +" ,??????????????????????????" ") `\\\n" // trigraphs suck +";' `.????????????????????????" "( `\\__\n" // trigraphs suck +" ; `.?????????????__..---'' `~~~~-._\n" +" `. `.____...--'' (b `--._\n" +" > _.-' .(( ._ )\n" +" .`.-`--...__ .-' -.___.....-(|/|/|/|/'\n" +" ;.'?????????`. ...----`.___.',,,_______......---'\n" +" '???????????" "'-'\n", // trigraphs suck + +" \n" +" \n" +" \n" +" \n" +" \n" +" cR \n" +" \n" +" cWWWWWWWW \n" +" \n" +" \n", + +" __\n" +" /' )\n" +" /' (??????????????????????????,\n" +" __/' )????????????????????????.' `;\n" +" _.-~~~~' ``---..__?????????????.' ;\n" +" _.--' b) ``--...____.' .'\n" +"( _. )). `-._ <\n" +" `\\|\\|\\|\\|)-.....___.- `-. __...--'-.'.\n" +" `---......_______,,,`.___.'----... .'?????????`.;\n" +" `-`???????????`\n", + +" \n" +" \n" +" \n" +" \n" +" \n" +" Rc \n" +" \n" +" WWWWWWWWc \n" +" \n" +" \n" + }; + + int shark_num = intRand(ARRAY_SIZE(shark_image)/2); + int shark_index = shark_num * 2; + QString color_mask = randColor(shark_image[shark_index+1]); + Frame sharkFrame(shark_image[shark_index], color_mask, 0x18B2B2); + + int x = -53; + int y = 9 + intRand(screen->height() - (10 + 9)); + int dir = (shark_num % 2) ? -1 : 1; + + if(dir < 0) + x = screen->width() - 2; + + RandomMovingSprite* shark = new RandomMovingSprite(screen, dir, 2, x, y, 2 /* Always at 2 */); + shark->addFrame(sharkFrame); + screen->addSprite(shark); + + TeethSprite *teeth = new TeethSprite(shark); + screen->addSprite(teeth); +} + +void AASaver::addBubble(Screen *screen, int x, int y, int z) +{ + screen->addSprite(new AirBubble(screen, x, y, z)); +} + +void AASaver::addShip(Screen* screen) +{ + QString ship_image[] = { +" | | |\n" +" )_) )_) )_)\n" +" )___))___))___)\\\n" +" )____)____)_____)\\\\\n" +"_____|____|____|____\\\\\\__\n" +"\\ /", + +" y y y\n" +" \n" +" w\n" +" ww\n" +"yyyyyyyyyyyyyyyyyyyywwwyy\n" +"y y", + +" | | |\n" +" (_( (_( (_(\n" +" /(___((___((___(\n" +" //(_____(____(____(\n" +"__///____|____|____|_____\n" +" \\ /", + +" y y y\n" +" \n" +" w \n" +" ww \n" +"yywwwyyyyyyyyyyyyyyyyyyyy\n" +" y y" + }; + + int ship_num = intRand(17) % 2; // right == 0, left == 1 + int x = -24, dir = 1; + + if(ship_num == 1) { + x = screen->width() - 2; + dir = -1; + } + + RandomMovingSprite *ship = new RandomMovingSprite(screen, dir, 1.0, x, 0, 2); + ship->addFrame(Frame(ship_image[2 * ship_num], ship_image[2 * ship_num + 1], 0xFFFFFF)); + screen->addSprite(ship); +} + +void AASaver::addWhale(Screen* screen) +{ + QString whale_image[] = { +" .-----:\n" +" .' `.\n" +",????/ (o) \\\n" +"\\`._/ ,__)", + +" C C\n" +" CCCCCCC\n" +" C C C\n" +" BBBBBBB\n" +" BB BB\n" +"B B BWB B\n" +"BBBBB BBBB", + +" :-----.\n" +" .' `.\n" +" / (o) \\????,\n" +"(__, \\_.'/", + +" C C\n" +" CCCCCCC\n" +" C C C\n" +" BBBBBBB\n" +" BB BB\n" +" B BWB B B\n" +"BBBB BBBBB" + }; + + QString spouty[] = { +"\n" +"\n" +" :", + +"\n" +" :\n" +" :", + +" . .\n" +" -:-\n" +" :", + +" . .\n" +" .-:-.\n" +" :", + +" . .\n" +"'.-:-.`\n" +"' : '", + +"\n" +" .- -.\n" +"; : ;", + +"\n" +"\n" +"; ;" + }; + + int whale_num = intRand(2); // 0 = right, 1 = left + int x = -18, spout_align = 11, dir = 1; + + if (whale_num == 1) + { + x = screen->width() - 2; + spout_align = 1; // Waterspout closer to left side now. + dir = -1; + } + + QString mask = whale_image[2 * whale_num + 1]; + + RandomMovingSprite *whale = new RandomMovingSprite(screen, dir, 1.0, x, 0, 2); + whale->setFrameDelay(80); + whale->setFrameTime(40); + + // We have to add some frames now. The first five will have no water spout. + QString blankWhaleFrame = QString("\n\n\n") + whale_image[2 * whale_num]; + + for(unsigned i = 0; i < 5; ++i) + whale->addFrame(Frame(blankWhaleFrame, mask, 0xFFFFFF)); + + // Now add frames for the animated water spout. + QString whaleFrame = whale_image[2 * whale_num]; + for (unsigned i = 0; i < ARRAY_SIZE(spouty); ++i) + { + QStringList spoutLines = QStringList::split("\n", spouty[i], true); + QString spout; + QString padding; + + padding.fill(' ', spout_align); + + // Move spout over an appropriate distance to line up right. + for(QStringList::ConstIterator it = spoutLines.begin(); it != spoutLines.end(); ++it) + { + spout += padding; + spout += *it; + spout += "\n"; + } + + // Add spout to whale frame. + whale->addFrame(Frame(spout + whaleFrame, mask, 0xFFFFFF)); + } + + screen->addSprite(whale); +} + +void AASaver::addBigFish(Screen* screen) +{ + QString big_fish_image[] = { +" ______\n" +"`\"\"-. `````-----.....__\n" +" `. . . `-.\n" +" : . . `.\n" +" ,?????: . . _ :\n" +": `.???: (@) `._\n" +" `. `..' . =`-. .__)\n" +" ; . = ~ : .-\"\n" +" .' .'`. . . =.-' `._ .'\n" +": .'???: . .'\n" +" '???.' . . . .-'\n" +" .'____....----''.'=.'\n" +" \"\"?????????????.'.'\n" +" ''\"'`", + +" 111111\n" +"11111 11111111111111111\n" +" 11 2 2 111\n" +" 1 2 2 11\n" +" 1 1 2 2 1 1\n" +"1 11 1 1W1 111\n" +" 11 1111 2 1111 1111\n" +" 1 2 1 1 1 111\n" +" 11 1111 2 2 1111 111 11\n" +"1 11 1 2 11\n" +" 1 11 2 2 2 111\n" +" 111111111111111111111\n" +" 11 1111\n" +" 11111", + +" ______\n" +" __.....-----''''' .-\"\"'\n" +" .-' . . .'\n" +" .' . . :\n" +" : _ . . :?????,\n" +" _.' (@) :???.' :\n" +"(__. .-'= . `..' .'\n" +" \"-. : ~ = . ;\n" +" `. _.' `-.= . . .'`. `.\n" +" `. . :???`. :\n" +" `-. . . . `.???`\n" +" `.=`.``----....____`.\n" +" `.`.?????????????\"\"\n" +" '`\"``", + +" 111111\n" +" 11111111111111111 11111\n" +" 111 2 2 11\n" +" 11 2 2 1\n" +" 1 1 2 2 1 1\n" +" 111 1W1 1 11 1\n" +"1111 1111 2 1111 11\n" +" 111 1 1 1 2 1\n" +" 11 111 1111 2 2 1111 11\n" +" 11 2 1 11 1\n" +" 111 2 2 2 11 1\n" +" 111111111111111111111\n" +" 1111 11\n" +" 11111" + }; + + int big_fish_num = intRand(2); // right = 0, left = 1 + + int maxHeight = 9, minHeight = screen->height() - 15; + int y = intRand(minHeight - maxHeight) + maxHeight; + int x = -34, dir = 1; + + if(big_fish_num == 1) + { + x = screen->width() - 1; + dir = -1; + } + + QString colors = randColor(big_fish_image[2 * big_fish_num + 1]); + RandomMovingSprite *bigFish = new RandomMovingSprite(screen, dir, 3.0, x, y, 2); + bigFish->addFrame(Frame(big_fish_image[2 * big_fish_num], colors, 0xFFFF54)); + + screen->addSprite(bigFish); +} + +void AASaver::addNessie(Screen* screen) +{ + QString nessie_image[] = { +" ____\n" +" __??????????????????????????????????????????/ o \\\n" +" / \\????????_?????????????????????_???????/ ____ >\n" +" _??????| __ |?????/ \\????????_????????/ \\????| |\n" +" | \\?????| || |????| |?????/ \\?????| |???| |", + +" ____\n" +" __?????????/ o \\\n" +" _?????????????????????_???????/ \\?????/ ____ >\n" +" _???????/ \\????????_????????/ \\????| __ |???| |\n" +" | \\?????| |?????/ \\?????| |???| || |???| |\n", + +" ____\n" +" __????????????????????/ o \\\n" +" _??????????????????????_???????/ \\????????_???????/ ____ >\n" +"| \\??????????_????????/ \\????| __ |?????/ \\????| |\n" +" \\ \\???????/ \\?????| |???| || |????| |???| |", + +" ____\n" +" __???????????????????????????????/ o \\\n" +" _??????????_???????/ \\????????_??????????????????/ ____ >\n" +" | \\???????/ \\????| __ |?????/ \\????????_??????| |\n" +" \\ \\?????| |???| || |????| |?????/ \\????| |", + +" ____\n" +" / o \\??????????????????????????????????????????__\n" +"< ____ \\???????_?????????????????????_????????/ \\\n" +" | |????/ \\????????_????????/ \\?????| __ |??????_\n" +" | |???| |?????/ \\?????| |????| || |?????/ |", + +" ____\n" +" / o \\?????????__\n" +"< ____ \\?????/ \\???????_?????????????????????_\n" +" | |???| __ |????/ \\????????_????????/ \\???????_\n" +" | |???| || |???| |?????/ \\?????| |?????/ |", + +" ____\n" +" / o \\????????????????????__\n" +"< ____ \\???????_????????/ \\???????_??????????????????????_\n" +" | |????/ \\?????| __ |????/ \\????????_??????????/ |\n" +" | |???| |????| || |???| |?????/ \\???????/ /", + +" ____\n" +" / o \\???????????????????????????????__\n" +"< ____ \\??????????????????_????????/ \\???????_??????????_\n" +" | |??????_????????/ \\?????| __ |????/ \\???????/ |\n" +" | |????/ \\?????| |????| || |???| |?????/ /" + }; + + QString nessie_mask[] = { +"\n" +" W\n" +"\n" +"\n" +"\n" +"", + +"\n" +" W\n" +"\n" +"\n" +"\n" +"" + }; + + int nessie_num = intRand(2); // 0 = right, 1 = left. + int x = -64, dir = 1; + + if(nessie_num == 1) { + x = screen->width() - 2; + dir = -1; + } + + RandomMovingSprite *nessie = new RandomMovingSprite(screen, dir, 1.4, x, 2, 2); + nessie->setFrameDelay(75); + nessie->setFrameTime(400); + + for(unsigned i = 0; i < 4; ++i) + nessie->addFrame(Frame(nessie_image[nessie_num * 4 + i], nessie_mask[nessie_num], 0x18B218)); + + screen->addSprite(nessie); +} + +void AASaver::addRandom(Screen* screen) +{ + const char *const cute_messages[] = { + "Quick, someone cue the ominous music!", + "Her continuing mission... to explore strange new seas...", + "I caught one that big once, but it got away. :(", + "Nessie, an Earthbound hero's best friend...", + "Thar be WHALES, Cap'n!!" + }; + int choice = intRand(5); + + if(intRand(45) < 7 && choice < ARRAY_SIZE(cute_messages)) + kdDebug() << cute_messages[choice] << endl; + + switch(choice) + { + case 0: + addShark(screen); + break; + case 1: + addShip(screen); + break; + case 2: + addBigFish(screen); + break; + case 3: + addNessie(screen); + break; + case 4: + addWhale(screen); + break; + } +} + +void AASaver::paintEvent(QPaintEvent* pe) +{ + screen->paint(pe->region()); +} + +// libkscreensaver interface +extern "C" +{ + KDE_EXPORT const char *kss_applicationName = "asciiquarium.kss"; + KDE_EXPORT const char *kss_description = I18N_NOOP( "Asciiquarium" ); + KDE_EXPORT const char *kss_version = "0.3.2"; + + KDE_EXPORT KScreenSaver *kss_create( WId id ) + { + return new AASaver( id ); + } + + KDE_EXPORT QDialog *kss_setup() + { + KConfigDialog *dialog = KConfigDialog::exists("settings"); + if(dialog) + return dialog; + + dialog = new KConfigDialog(0, "settings", AASaverConfig::self()); + SettingsWidget *settings = new SettingsWidget(0, "settings_widget"); + + dialog->addPage(settings, i18n("Asciiquarium Settings"), "kscreensaver"); + + return dialog; + } +} + + +// vim: set et ts=8 sw=4: diff --git a/asciiquarium/src/aasaver.h b/asciiquarium/src/aasaver.h new file mode 100644 index 00000000..131b67b9 --- /dev/null +++ b/asciiquarium/src/aasaver.h @@ -0,0 +1,199 @@ +/* + * Asciiquarium - Native KDE Screensaver based on the Asciiquarium program + * (c) Kirk Baucom , which you can find at + * http://www.robobunny.com/projects/asciiquarium/ + * + * Ported to KDE by Maksim Orlovich and + * Michael Pyne . + * + * Copyright (c) 2003 Kirk Baucom + * Copyright (c) 2005 Maksim Orlovich + * Copyright (c) 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef AA_AASAVER_H +#define AA_AASAVER_H + +#include +#include + +#include + +class Screen; +class Sprite; + +/** + * \mainpage Asciiquarium. + * + * \section intro Introduction + * + * Asciiquarium is a KDE screensaver to draw an ASCII art aquarium. This is the + * documentation of the API used in the program to generate the effect. It should + * be fairly simple, but basically: + * + * class AASaver is the main class, which handles outside events. All of the + * processing happens in the Screen class however, which manages a list of + * Sprites, updating them and drawing them as needed. When AASaver receives a + * paintEvent(), it forwards it on to Screen to handle it. + * + * Each Sprite is composed of 1 or more Frames. When a Screen wants a Sprite + * to draw itself, the Sprite forwards the request to its currently shown Frame. + * + * The Frame is rectangular, and created from textual ASCII art, with a ASCII + * art shape and color mask. The mask is optional. See aasaver.cpp for + * examples for creating a Frame. + * + * The Frame supports transparency and colors, and will convert the textual data + * into a QPixmap representation on demand in order to reduce CPU load (at the + * expense of a slight memory usage increase for each sprite). + * + * Screen handles the timing for the project, and at each timeout will call + * Sprite::tickUpdate() from Screen::doAnimate(). + * + * This whole program was inspired/copied from Kirk Baucom's asciiquarium + * program, from http://www.robobunny.com/projects/asciiquarium/ + */ + +/** + * The main class for the Asciiquarium screensaver. + */ +class AASaver: public KScreenSaver +{ + /// Handles the animation and drawing. + Screen* screen; + +public: + /// Construct the screensaver with window id \p id. + AASaver( WId id ); + + /// Returns a random double between [0.0, limit). + static double doubleRand(double limit) + { + return (limit * (static_cast(KApplication::random()) / RAND_MAX)); + } + + /// Returns a random integer between [0, limit) + static int intRand(int limit) + { + return KApplication::random() % limit; + } + + /** + * Returns a QString holding a color mask, created by choosing random colors + * to replace numbers in \p color_mask. + */ + static QString randColor(QString color_mask); + + /// Adds the castle sprite to the screen. + void addCastle(); + + /// Adds the environment (sea, etc.) to the screen. + void addEnvironment(); + + /// Adds the seaweed to the screen. + void addAllSeaweed(); + + /// Adds the initial layout of fish to the sea, scaling the number of fish + /// based on the current screen size. + void addAllFish(); + + /** + * Adds a seaweed to a random position of the sea bottom. + * + * @param screen The Screen to add into. + */ + static void addSeaweed(Screen* screen); + + /** + * Returns a new fish sprite, which has not yet been added to a screen. + * + * @param screen The Screen to use when constructing the Sprite. + * @todo Combine with addFish(). + */ + static Sprite *newFish(Screen *screen); + + /** + * Adds a new fish sprite to \p screen. + * + * @param screen The Screen to add a fish to. + */ + static void addFish(Screen *screen); + + /** + * Adds a new air bubble sprite to \p screen. The \p x, \p y, and \p z + * coordinates are all in logical coordinates. + * + * @param screen The Screen to add the bubble to. + * @param x The x position to start the bubble at. + * @param y The y position to start the bubble at. + * @param z The z position to start the bubble at. + */ + static void addBubble(Screen* screen, int x, int y, int z); + + /** + * Adds a Nessie, the Loch Ness Monster sprite to \p screen. + * + * @param screen The Screen to add Nessie to. + */ + static void addNessie(Screen* screen); + + /** + * Adds a big fish sprite to \p screen. + * + * @param screen The Screen to add the big fish to. + */ + static void addBigFish(Screen* screen); + + /** + * Adds a whale sprite to \p screen. + * + * @param screen The Screen to add the whale to. + */ + static void addWhale(Screen* screen); + + /** + * Adds a shark sprite to \p screen. The shark can kill() fish it comes in + * contact with (they will spawn more fish automatically). + * + * @param screen The Screen to add the shark to. + */ + static void addShark(Screen* screen); + + /** + * Adds a ship sprite to \p screen. + * + * @param screen The Screen to add the ship to. + */ + static void addShip(Screen* screen); + + /** + * Adds a random object from the set (Shark, Big Fish, Nessie, Whale, Ship) + * to the sea. + * + * @param screen The Screen to add to. + */ + static void addRandom(Screen* screen); + + /** + * Reimplemented to update the widget when it gets dirty. + */ + virtual void paintEvent(QPaintEvent* pe); +}; + +#endif /* AA_AASAVER_H */ + +// vim: set et ts=8 sw=4: diff --git a/asciiquarium/src/asciiquarium.desktop b/asciiquarium/src/asciiquarium.desktop new file mode 100644 index 00000000..e73d64d3 --- /dev/null +++ b/asciiquarium/src/asciiquarium.desktop @@ -0,0 +1,23 @@ +[Desktop Entry] +Encoding=UTF-8 +Exec=asciiquarium.kss +Icon=kscreensaver +Type=Application +Actions=InWindow;Root;Setup +Name=Asciiquarium +X-KDE-Category=Miscellaneous + +[Desktop Action InWindow] +Exec=asciiquarium.kss -window-id %w +Name=Display in specified window +NoDisplay=true + +[Desktop Action Root] +Exec=asciiquarium.kss -root +Name=Display in root window +NoDisplay=true + +[Desktop Action Setup] +Exec=asciiquarium.kss -setup +Name=Display setup dialog +NoDisplay=true diff --git a/asciiquarium/src/asciiquarium.kcfg b/asciiquarium/src/asciiquarium.kcfg new file mode 100644 index 00000000..244a1197 --- /dev/null +++ b/asciiquarium/src/asciiquarium.kcfg @@ -0,0 +1,14 @@ + + + + + + + + 20 + You can use this value to select the number of fish that will be + on screen at a given time. + + + + diff --git a/asciiquarium/src/doxygen-stylesheet.css b/asciiquarium/src/doxygen-stylesheet.css new file mode 100644 index 00000000..b42407b6 --- /dev/null +++ b/asciiquarium/src/doxygen-stylesheet.css @@ -0,0 +1,309 @@ +BODY,H1,H2,H3,H4,H5,H6,P,CENTER,TD,TH,UL,DL,DIV { + font-family: sans-serif, Geneva, Arial, Helvetica; +} +BODY,TD { + font-size: 100%; +} +H1 { + text-align: center; + font-size: 160%; +} +H2 { + font-size: 120%; +} +H3 { + font-size: 110%; +} +CAPTION { font-weight: bold } +DIV.qindex { + width: 100%; + background-color: #eeeeff; + border: 1px solid #b0b0b0; + text-align: center; + margin: 2px; + padding: 2px; + line-height: 140%; +} +DIV.nav { + width: 100%; + background-color: #eeeeff; + border: 1px solid #b0b0b0; + text-align: center; + margin: 2px; + padding: 2px; + line-height: 140%; +} +DIV.navtab { + background-color: #eeeeff; + border: 1px solid #b0b0b0; + text-align: center; + margin: 2px; + margin-right: 15px; + padding: 2px; +} +TD.navtab { + font-size: 80%; +} +A.qindex { + text-decoration: none; + font-weight: bold; + color: #1A419D; +} +A.qindex:visited { + text-decoration: none; + font-weight: bold; + color: #1A419D +} +A.qindex:hover { + text-decoration: none; + background-color: #ddddff; +} +A.qindexHL { + text-decoration: none; + font-weight: bold; + background-color: #6666cc; + color: #ffffff; + border: 1px double #9295C2; +} +A.qindexHL:hover { + text-decoration: none; + background-color: #6666cc; + color: #ffffff; +} +A.qindexHL:visited { text-decoration: none; background-color: #6666cc; color: #ffffff } +A.el { text-decoration: none; font-weight: bold } +A.elRef { font-weight: bold } +A.code:link { text-decoration: none; font-weight: normal; color: #0000FF} +A.code:visited { text-decoration: none; font-weight: normal; color: #0000FF} +A.codeRef:link { font-weight: normal; color: #0000FF} +A.codeRef:visited { font-weight: normal; color: #0000FF} +A:hover { text-decoration: none; background-color: #f2f2ff } +DL.el { margin-left: -1cm } +.fragment { + font-family: monospace; + font-size: 105%; +} +PRE.fragment { + border: 1px solid #CCCCCC; + background-color: #f5f5f5; + margin-top: 4px; + margin-bottom: 4px; + margin-left: 2px; + margin-right: 8px; + padding-left: 6px; + padding-right: 6px; + padding-top: 4px; + padding-bottom: 4px; +} +DIV.ah { background-color: black; font-weight: bold; color: #ffffff; margin-bottom: 3px; margin-top: 3px } +TD.md { background-color: #F4F4FB; font-weight: bold; } +TD.mdPrefix { + background-color: #F4F4FB; + color: #606060; + font-size: 90%; +} +TD.mdname1 { background-color: #F4F4FB; font-weight: bold; color: #602020; } +TD.mdname { background-color: #F4F4FB; font-weight: bold; color: #602020; width: 600px; } +DIV.groupHeader { + margin-left: 16px; + margin-top: 12px; + margin-bottom: 6px; + font-weight: bold; +} +DIV.groupText { margin-left: 16px; font-style: italic; font-size: 95% } +BODY { + background: white; + color: black; + margin-right: 20px; + margin-left: 20px; +} +TD.indexkey { + background-color: #eeeeff; + font-weight: bold; + padding-right : 10px; + padding-top : 2px; + padding-left : 10px; + padding-bottom : 2px; + margin-left : 0px; + margin-right : 0px; + margin-top : 2px; + margin-bottom : 2px; + border: 1px solid #CCCCCC; +} +TD.indexvalue { + background-color: #eeeeff; + font-style: italic; + padding-right : 10px; + padding-top : 2px; + padding-left : 10px; + padding-bottom : 2px; + margin-left : 0px; + margin-right : 0px; + margin-top : 2px; + margin-bottom : 2px; + border: 1px solid #CCCCCC; +} +TR.memlist { + background-color: #f0f0f0; +} +P.formulaDsp { text-align: center; } +IMG.formulaDsp { } +IMG.formulaInl { vertical-align: middle; } +SPAN.keyword { color: #008000 } +SPAN.keywordtype { color: #604020 } +SPAN.keywordflow { color: #e08000 } +SPAN.comment { color: #800000 } +SPAN.preprocessor { color: #806020 } +SPAN.stringliteral { color: #002080 } +SPAN.charliteral { color: #008080 } +.mdTable { + border: 1px solid #868686; + background-color: #F4F4FB; +} +.mdRow { + padding: 8px 10px; +} +.mdescLeft { + padding: 0px 8px 4px 8px; + font-size: 80%; + font-style: italic; + background-color: #FAFAFA; + border-top: 1px none #E0E0E0; + border-right: 1px none #E0E0E0; + border-bottom: 1px none #E0E0E0; + border-left: 1px none #E0E0E0; + margin: 0px; +} +.mdescRight { + padding: 0px 8px 4px 8px; + font-size: 80%; + font-style: italic; + background-color: #FAFAFA; + border-top: 1px none #E0E0E0; + border-right: 1px none #E0E0E0; + border-bottom: 1px none #E0E0E0; + border-left: 1px none #E0E0E0; + margin: 0px; +} +.memItemLeft { + padding: 1px 0px 0px 8px; + margin: 4px; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + border-top-color: #E0E0E0; + border-right-color: #E0E0E0; + border-bottom-color: #E0E0E0; + border-left-color: #E0E0E0; + border-top-style: solid; + border-right-style: none; + border-bottom-style: none; + border-left-style: none; + background-color: #FAFAFA; + font-size: 80%; +} +.memItemRight { + padding: 1px 8px 0px 8px; + margin: 4px; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + border-top-color: #E0E0E0; + border-right-color: #E0E0E0; + border-bottom-color: #E0E0E0; + border-left-color: #E0E0E0; + border-top-style: solid; + border-right-style: none; + border-bottom-style: none; + border-left-style: none; + background-color: #FAFAFA; + font-size: 80%; +} +.memTemplItemLeft { + padding: 1px 0px 0px 8px; + margin: 4px; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + border-top-color: #E0E0E0; + border-right-color: #E0E0E0; + border-bottom-color: #E0E0E0; + border-left-color: #E0E0E0; + border-top-style: none; + border-right-style: none; + border-bottom-style: none; + border-left-style: none; + background-color: #FAFAFA; + font-size: 80%; +} +.memTemplItemRight { + padding: 1px 8px 0px 8px; + margin: 4px; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + border-top-color: #E0E0E0; + border-right-color: #E0E0E0; + border-bottom-color: #E0E0E0; + border-left-color: #E0E0E0; + border-top-style: none; + border-right-style: none; + border-bottom-style: none; + border-left-style: none; + background-color: #FAFAFA; + font-size: 80%; +} +.memTemplParams { + padding: 1px 0px 0px 8px; + margin: 4px; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + border-top-color: #E0E0E0; + border-right-color: #E0E0E0; + border-bottom-color: #E0E0E0; + border-left-color: #E0E0E0; + border-top-style: solid; + border-right-style: none; + border-bottom-style: none; + border-left-style: none; + color: #606060; + background-color: #FAFAFA; + font-size: 80%; +} +.search { color: #003399; + font-weight: bold; +} +FORM.search { + margin-bottom: 0px; + margin-top: 0px; +} +INPUT.search { font-size: 75%; + color: #000080; + font-weight: normal; + background-color: #eeeeff; +} +TD.tiny { font-size: 75%; +} +a { + color: #252E78; +} +a:visited { + color: #3D2185; +} +.dirtab { padding: 4px; + border-collapse: collapse; + border: 1px solid #b0b0b0; +} +TH.dirtab { background: #eeeeff; + font-weight: bold; +} +HR { height: 1px; + border: none; + border-top: 1px solid black; +} diff --git a/asciiquarium/src/frame.cpp b/asciiquarium/src/frame.cpp new file mode 100644 index 00000000..da0d006f --- /dev/null +++ b/asciiquarium/src/frame.cpp @@ -0,0 +1,204 @@ +/* + * Asciiquarium - Native KDE Screensaver based on the Asciiquarium program + * (c) Kirk Baucom , which you can find at + * http://www.robobunny.com/projects/asciiquarium/ + * + * Ported to KDE by Maksim Orlovich and + * Michael Pyne . + * + * Copyright (c) 2003 Kirk Baucom + * Copyright (c) 2005 Maksim Orlovich + * Copyright (c) 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "frame.h" + +void Frame::convertDataToPixmap(const Screen *screen) +{ + if(!height() || !width()) { + // Assume we're not ready to go. + return; + } + + int w = screen->cellWidth(), h = screen->cellHeight(); + QPixmap pix(width() * w, height() * h); + pix.fill(); + + QBitmap mask(pix.size(), true); + + QPainter p, p2; + + p.begin(&pix, true); + p2.begin(&mask, true); + + p.setFont(KGlobalSettings::fixedFont()); + QFontMetrics fm(p.font()); + int leadHeight = fm.leading() + fm.descent(); + + for(unsigned j = 0; j < m_data.count(); ++j) { + QValueVector row = m_data[j]; + if(row.isEmpty()) + continue; + + unsigned first, last; + for (first = 0; first < row.count() && row[first].letter == ' '; ++first) + ; + + last = row.count() - 1; // Assume the end is already stripped. + + for(unsigned i = first; i <= last; ++i) { + if(row[i].letter == m_transparentChar) + continue; + + p2.fillRect(i * w, j * h, w, h, Qt::color1); + + p.setPen(row[i].color); + p.fillRect(i * w, j * h, w, h, Qt::black); + p.drawText(i * w, j * h + (h - 1 - leadHeight), QChar(row[i].letter)); + } + } + + pix.setMask(mask); + + QPixmap erase(pix); + erase.fill(Qt::black); + erase.setMask(mask); + + m_pixmap = pix; + m_erasePixmap = erase; + + // Clear m_data to save a wee bit of memory. + m_data.clear(); +} + +Frame::Frame (QString text, QString mask, QRgb defaultColor, QChar transparent) +{ + //First, process the pixels. + + QStringList rows = QStringList::split('\n', text, true); + m_height = rows.size(); + m_width = 0; + m_transparentChar = transparent; + + for (QStringList::iterator i = rows.begin(); i != rows.end(); ++i) + { + QValueVector row; + for (int pos = 0; pos < (*i).length(); ++pos) + { + Screen::Pixel p; + p.letter = (*i).at(pos).unicode(); + p.color = defaultColor; + row.append(p); + } + + m_width = QMAX(m_width, row.size()); + m_data.append(row); + } + + //Now, the colors. + QStringList cols = QStringList::split('\n', mask, true); + int y = 0; + for (QStringList::iterator i = cols.begin(); i != cols.end(); ++i) + { + if (y >= m_data.size()) + break; + + for (int pos = 0; pos < (*i).length() && pos < m_data[y].size(); ++pos) + { + switch ((*i).at(pos).unicode()) + { + //Colors stolen from konsole, TEWidget.cpp + case 'R': + m_data[y][pos].color = 0xFF5454; + break; + case 'r': + m_data[y][pos].color = 0xB21818; + break; + case 'C': + m_data[y][pos].color = 0x54FFFF; + break; + case 'c': + m_data[y][pos].color = 0x18B2B2; + break; + case 'Y': + m_data[y][pos].color = 0xFFFF54; + break; + case 'y': + m_data[y][pos].color = 0xB26818; + break; + case 'G': + m_data[y][pos].color = 0x54FF54; + break; + case 'g': + m_data[y][pos].color = 0x18B218; + break; + case 'B': + m_data[y][pos].color = 0x5454FF; + break; + case 'b': + m_data[y][pos].color = 0x1818B2; + break; + case 'M': + m_data[y][pos].color = 0xFF54FF; + break; + case 'm': + m_data[y][pos].color = 0xB218B2; + break; + case 'W': + m_data[y][pos].color = 0xFFFFFF; + break; + case 'w': + m_data[y][pos].color = 0xB2B2B2; + break; + case ' ': + break; + default: + qDebug("dunno about color code:'%c'", (*i).at(pos).unicode()); + m_data[y][pos].color = 0xFFFFFF; + } + } + ++y; + } +} + +void Frame::paint(Screen* scr, int x, int y) +{ + if(m_pixmap.isNull()) + convertDataToPixmap(scr); + + scr->updateSpan(x, y, m_pixmap); +} + +void Frame::erase(Screen* scr, int x, int y) +{ + if(m_erasePixmap.isNull()) + convertDataToPixmap(scr); + + scr->clearSpan(x, y, m_erasePixmap); +} + +// vim: set et ts=8 sw=4: diff --git a/asciiquarium/src/frame.h b/asciiquarium/src/frame.h new file mode 100644 index 00000000..a6ad82bd --- /dev/null +++ b/asciiquarium/src/frame.h @@ -0,0 +1,150 @@ +/* + * Asciiquarium - Native KDE Screensaver based on the Asciiquarium program + * (c) Kirk Baucom , which you can find at + * http://www.robobunny.com/projects/asciiquarium/ + * + * Ported to KDE by Maksim Orlovich and + * Michael Pyne . + * + * Copyright (c) 2003 Kirk Baucom + * Copyright (c) 2005 Maksim Orlovich + * Copyright (c) 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef AA_FRAME_H +#define AA_FRAME_H + +#include +#include +#include +#include "screen.h" + +/** + * Represents a single frame of a sprite's animation. + * + * @see Sprite + */ +class Frame +{ + /** + * Two-dimensional array of Pixels, which represent the appearance of this + * frame. This is used to create m_pixmap and m_erasePixmap when they are + * needed. + * + * @see Pixel + */ + QValueVector > m_data; + + /// Masked pixmap of the animation frame. Created by convertDataToPixmap(). + QPixmap m_pixmap; + + /// Masked pixmap used to clear frame. Created by convertDataToPixmap(). + QPixmap m_erasePixmap; + + /// Height of this frame of animation in logical coordinates. + int m_height; + + /// Width of this frame of animation in logical coordinates. + int m_width; + + /// Character to be used as a special 'transparent' character. Normally is + /// the '?' character. + QChar m_transparentChar; + +public: + + /** + * Constructs an empty animation Frame. Do not insert this into a Sprite. + */ + Frame() : m_height(0), m_width(0) + { + } + + /** + * Constructs an animation frame. + * + * @param text Newline-separated text used to construct the Pixel arrays. + * The lines do not have to be equal length, any extra needed + * characters will automatically be filled with transparency. + * Any whitespace at the beginning of a line is converted to + * transparency as well. + * + * @param mask Newline-separated text used to mask \p text's colors. This + * can be empty or null in which case no masking is performed. + * However, if present, there should be the same number of + * lines in \p mask as in \p text, although individual lines + * can be shorter or empty as convienient. You can use letters + * to stand for colors, e.g. 'r' will make the letter in \p + * text at the same position dark red. + * + * @param defaultColor The default color to apply to characters. This + * color is used for all characters in \p text that are + * not altered by \p mask. + * + * @param transparent The character to use to represent transparent areas + * in \p text. This can be useful when the + * auto-transparency feature can't detect transparent + * areas. + */ + Frame(QString text, QString mask, QRgb defaultColor, QChar transparent = '?'); + + /** + * Paints this Frame into the given screen. + * + * @param scr The Screen to draw into. + * @param x The logical x coordinate of the left edge of the update region. + * @param y The logical y coordinate of the top edge of the update region. + */ + void paint(Screen* scr, int x, int y); + + /** + * Erases this Frame from the given screen. + * + * @param scr The Screen to draw into. + * @param x The logical x coordinate of the left edge of the update region. + * @param y The logical y coordinate of the top edge of the update region. + */ + void erase(Screen* scr, int x, int y); + + /// Returns the logical width of this frame. + int width() const + { + return m_width; + } + + /// Returns the logical height of this frame. + int height() const + { + return m_height; + } + +protected: + + /** + * This function converts the Pixel data in m_data to setup m_pixmap + * and m_erasePixmap, which are not setup until this function is called. + * + * m_data is not valid after this call is performed to save memory. + * + * @param screen The Screen we will be drawing into later. + */ + void convertDataToPixmap(const Screen *screen); +}; + +#endif + +// vim: set et ts=8 sw=4: diff --git a/asciiquarium/src/screen.cpp b/asciiquarium/src/screen.cpp new file mode 100644 index 00000000..240502ff --- /dev/null +++ b/asciiquarium/src/screen.cpp @@ -0,0 +1,251 @@ +/* + * Asciiquarium - Native KDE Screensaver based on the Asciiquarium program + * (c) Kirk Baucom , which you can find at + * http://www.robobunny.com/projects/asciiquarium/ + * + * Ported to KDE by Maksim Orlovich and + * Michael Pyne . + * + * Copyright (c) 2003 Kirk Baucom + * Copyright (c) 2005 Maksim Orlovich + * Copyright (c) 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "screen.h" +#include "sprite.h" +#include "aasaver.h" + +Screen::Screen(AASaver* widget): m_widget(widget) +{ + QFontMetrics fm(KGlobalSettings::fixedFont()); + + // Compute cell geometries. + m_cellW = fm.maxWidth(); + m_cellH = fm.lineSpacing(); + + // Computer number of full cells that will fit. + m_width = widget->width() / m_cellW; + m_height = widget->height() / m_cellH; + + // Calculate offset needed to evenly distribute excess screen space. + m_offX = (widget->width() - m_width * m_cellW) / 2; + m_offY = (widget->height() - m_height * m_cellH) / 2; + + // Create double buffer. + m_backBuffer = QPixmap(m_widget->size()); + m_backBuffer.fill(black); + + // FIXME: handle resizing! + + // Setup animation timer. + QTimer* timer = new QTimer(this); + connect(timer, SIGNAL(timeout()), SLOT(doAnimate())); + + timer->start(msPerTick()); +} + +int Screen::msPerTick() const +{ + return 50; +} + +Screen::~Screen() +{ +} + +void Screen::updateSpan(int x, int y, const QPixmap &updatePixmap) +{ + if (y < 0 || y >= m_height) return; + + QPoint upperLeft(m_offX + x * m_cellW, m_offY + y * m_cellH); + bitBlt(&m_backBuffer, upperLeft, &updatePixmap, updatePixmap.rect(), Qt::CopyROP); + m_widget->update(QRect(upperLeft, updatePixmap.size())); +} + +void Screen::clearSpan(int x, int y, const QPixmap &clearPixmap) +{ + if (y < 0 || y >= m_height) return; + + QPoint upperLeft(m_offX + x * m_cellW, m_offY + y * m_cellH); + bitBlt(&m_backBuffer, upperLeft, &clearPixmap, clearPixmap.rect(), Qt::CopyROP); + m_widget->update(QRect(upperLeft, clearPixmap.size())); +} + +//Actually paints the region on the widget. +void Screen::paint(QRegion r) +{ + QPainter p(m_widget); + QMemArray rects = r.rects(); + + for (int r = 0; r < rects.size(); ++r) + { + //Determine the grid locations described by the rect + QRect bound = rects[r]; + + bitBlt(m_widget, bound.topLeft(), &m_backBuffer, bound, Qt::CopyROP); + } //for rect in region +}; + +/** + * Utility type used to faciliate sorting of the Sprite list in order to + * implement the Painter's Algorithm when painting the back buffer. + */ +struct ZKey +{ + /** + * Logical depth of sprite. Now 0 is farthest away from the eyes, unlike + * with Sprite::depth(). + */ + int z; + + Sprite* addr; + + ZKey(): z(0), addr(0) + {} + + ZKey(Sprite* spr): z(1000 - spr->depth()), addr(spr) + {} + + bool operator<(const ZKey& other) const + { + if (z < other.z) return true; + if (z > other.z) return false; + + return addr < other.addr; + } +}; + +void Screen::doAnimate() +{ + //First, rebuild a new list of sprites, and build a dirty region + QRegion dirtyRegion; + + QValueVector sprites; + QValueVector colliders; + + // Look for sprites that can suffer a collision. + for (unsigned pos = 0; pos < m_sprites.size(); ++pos) + { + if(m_sprites[pos]->canCollide()) + colliders.append(m_sprites[pos]); + } + + // Find collisions. + // FIXME: Use transparent regions for accuracy. + for (unsigned pos = 0; pos < colliders.size(); ++pos) + for (unsigned sprite = 0; sprite < m_sprites.size(); ++sprite) + { + if(m_sprites[sprite] == colliders[pos]) + continue; + + if(colliders[pos]->geom().intersects(m_sprites[sprite]->geom())) + colliders[pos]->collision(m_sprites[sprite]); + } + + //Retain all live existing sprites + for (int pos = 0; pos < m_sprites.size(); ++pos) + { + Sprite* sprite = m_sprites[pos]; + QRect oldRect = sprite->geom(); + if (!sprite->isKilled()) { + bool dirty = sprite->tickUpdate(); + + if (dirty) + dirtyRegion |= oldRect | sprite->geom(); + + if (!sprite->isKilled()) + sprites.append(sprite); + } + + if (sprite->isKilled()) //note:may be made true by updateTick! + { + dirtyRegion |= oldRect; + delete sprite; + } + } + + //Add new sprites. + for (int pos = 0; pos < m_addedSprites.size(); ++pos) + { + dirtyRegion |= m_addedSprites[pos]->geom(); + sprites.append(m_addedSprites[pos]); + } + + m_addedSprites.clear(); + m_sprites = sprites; + + //Compute the list of sprites affected. Note that this is + //done iteratively until fixed point. + QValueVector paintSprites; + QValueVector remSprites; + + bool changed; + do + { + changed = false; + remSprites.clear(); + + for (int c = 0; c < sprites.size(); ++c) + { + Sprite* sprite = sprites[c]; + + if (dirtyRegion.intersect(sprite->geom()).isEmpty()) + remSprites.append(sprite); //not to be painted thus far + else + { + //This sprite is to be painted + paintSprites.append(sprite); + + //make sure we repaint everything overlapping it + dirtyRegion |= sprite->geom(); + changed = true; + } + } + sprites = remSprites; + } + while (changed); + + //Z-sort the items. + QMap sorted; + for (int pos = 0; pos < paintSprites.size(); ++pos) + sorted[ZKey(paintSprites[pos])] = paintSprites[pos]; + + //Paint, in Z-order + for (QMapIterator i = sorted.begin(); + i != sorted.end(); ++i) + i.data()->paint(); + + // Make sure black strip at edge is still present. + if(!paintSprites.isEmpty()) + { + QPainter p(&m_backBuffer); + p.fillRect(m_backBuffer.width() - m_offX, 0, m_offX, m_backBuffer.height(), Qt::black); + } +} + +#include "screen.moc" + +// vim: set et ts=8 sw=4: diff --git a/asciiquarium/src/screen.h b/asciiquarium/src/screen.h new file mode 100644 index 00000000..0cd141c2 --- /dev/null +++ b/asciiquarium/src/screen.h @@ -0,0 +1,171 @@ +/* + * Asciiquarium - Native KDE Screensaver based on the Asciiquarium program + * (c) Kirk Baucom , which you can find at + * http://www.robobunny.com/projects/asciiquarium/ + * + * Ported to KDE by Maksim Orlovich and + * Michael Pyne . + * + * Copyright (c) 2003 Kirk Baucom + * Copyright (c) 2005 Maksim Orlovich + * Copyright (c) 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef AA_SCREEN_H +#define AA_SCREEN_H + +#include +#include +#include +#include +#include + +class Sprite; +class AASaver; + +/** + * This is the main display class of Asciiquarium. We use a pseudo-terminal-ish + * type coordinate system, where although this is a full fledged GUI application, + * Sprites and most external functions deal with logical text position + * coordinates instead of GUI coordinates. (x, y) starts in the upper-left of + * the real screen at (0, 0), and continues on to (width - 1, height - 1). + * + * Use addSprite() to add new Sprites to the Screen after you have created them + * and added their Frames. + */ +class Screen: public QObject +{ + Q_OBJECT + +public: + /** + * Represents a logical character on the Screen. + */ + struct Pixel { + char letter; ///< Character to display in the cell. + QRgb color; ///< Color to use for the cell. + + /// Default constructor. + Pixel(): letter(' '), color(0) + { + } + }; + +private: + AASaver* m_widget; ///< Widget that we should paint on. + int m_width; ///< Number of logical columns in the screen. + int m_height; ///< Number of logical rows on the screen. + int m_offX; ///< Number of pixels on left side needed to center image. + int m_offY; ///< Number of pixels on top side needed to center image. + + /** Pixmap cache of the image used to speed up rendering. All paints happen + * to the pixmap, which is then bitBlt()'ed to m_widget when the time comes + * to paint. + */ + QPixmap m_backBuffer; + + int m_cellW; ///< The GUI width of a character cell. + int m_cellH; ///< The GUI height of a character cell. + + QValueVector m_sprites; ///< List of Sprites on screen. + QValueVector m_addedSprites; ///< List of Sprites to be added next frame. + +private slots: + /** + * Handles updating the screen buffer to draw the next frame. + */ + void doAnimate(); + +public: + /** + * Constructor. + * + * @param widget The widget to draw on. + */ + Screen(AASaver* widget); + ~Screen(); + + /// Returns the logical width of the screen. + int width() const + { + return m_width; + } + + /// Returns the logical height of the screen. + int height() const + { + return m_height; + } + + /// Returns the GUI width of a character cell. + int cellWidth() const + { + return m_cellW; + } + + /// Returns the GUI height of a character cell. + int cellHeight() const + { + return m_cellH; + } + + /** + * Adds a sprite to the internal sprite list. + * + * @param sprite The Sprite to add. It will show up in the next frame. + */ + void addSprite(Sprite* sprite) + { + m_addedSprites.append(sprite); + } + + /// Returns the number of milliseconds separating each animation tick. + int msPerTick() const; + + /** + * Updates the backbuffer, and asks the portion of the widget to be + * repainted. + * + * @param x The logical x coordinate of the left edge of the update area. + * @param y The logical y coordinate of the top edge of the update area. + * @param updatePixmap The pixmap to draw into the buffer, which should be + * masked to only draw non-transparent regions. + */ + void updateSpan(int x, int y, const QPixmap &updatePixmap); + + /** + * Clear the given portion of the backbuffer, asks for a repaint. + * + * @param x The logical x coordinate of the left edge of the update region. + * @param y The logical y coordinate of the top edge of the update region. + * @param clearPixmap the pixmap to use to clear the span, which should be + * the background color of the Screen, and masked to + * only draw the area that needs cleared. + */ + void clearSpan(int x, int y, const QPixmap &clearPixmap); + + /** + * Actually paints the region on the widget. + * + * @param r The region of the widget to update. + */ + void paint(QRegion r); +}; + +#endif + +// vim: set et ts=8 sw=4: diff --git a/asciiquarium/src/settingswidget.ui b/asciiquarium/src/settingswidget.ui new file mode 100644 index 00000000..6ab05a16 --- /dev/null +++ b/asciiquarium/src/settingswidget.ui @@ -0,0 +1,50 @@ + +SettingsWidget + + + SettingsWidget + + + + 0 + 0 + 399 + 45 + + + + Asciiquarium Settings + + + + unnamed + + + + textLabel1 + + + Number of Fish: + + + + + kcfg_fishCount + + + 50 + + + 1 + + + 5 + + + 15 + + + + + + diff --git a/asciiquarium/src/sprite.cpp b/asciiquarium/src/sprite.cpp new file mode 100644 index 00000000..85e946ed --- /dev/null +++ b/asciiquarium/src/sprite.cpp @@ -0,0 +1,94 @@ +/* + * Asciiquarium - Native KDE Screensaver based on the Asciiquarium program + * (c) Kirk Baucom , which you can find at + * http://www.robobunny.com/projects/asciiquarium/ + * + * Ported to KDE by Maksim Orlovich and + * Michael Pyne . + * + * Copyright (c) 2003 Kirk Baucom + * Copyright (c) 2005 Maksim Orlovich + * Copyright (c) 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "sprite.h" + +Sprite::Sprite(Screen* screen, int x, int y, int z, int frameDelay): + m_screen(screen), m_currentFrame(0), m_x(x), m_y(y), m_z(z), + m_isKilled(false), m_killAfterLastFrame(false), + m_ticksSinceFrameChange(0), m_frameDelay(frameDelay) +{ +} + +void Sprite::addFrame(const Frame& frame) +{ + m_frames.append(frame); +} + +void Sprite::erase() +{ + m_frames[m_currentFrame].erase(m_screen, m_x, m_y); +} + +void Sprite::paint() +{ + m_frames[m_currentFrame].paint(m_screen, m_x, m_y); +} + +bool Sprite::timerTick() +{ + ++m_ticksSinceFrameChange; + if (m_ticksSinceFrameChange * m_screen->msPerTick() < m_frameDelay) + return false; + + //Ring! Ring! + m_ticksSinceFrameChange = 0; + return true; +} + +bool Sprite::tickUpdate() +{ + if (m_frames.size() == 1) + return false; + + if (!timerTick()) + return false; + + erase(); + + ++m_currentFrame; + if (m_currentFrame == m_frames.size()) + { + m_currentFrame = 0; + + if(m_killAfterLastFrame) + { + erase(); + kill(); + } + } + + return true; +} + +QRect Sprite::geom() const +{ + return QRect(m_x, m_y, m_frames[0].width(), m_frames[0].height()); +} + + +// vim: set et ts=8 sw=4: diff --git a/asciiquarium/src/sprite.h b/asciiquarium/src/sprite.h new file mode 100644 index 00000000..14fb1c19 --- /dev/null +++ b/asciiquarium/src/sprite.h @@ -0,0 +1,207 @@ +/* + * Asciiquarium - Native KDE Screensaver based on the Asciiquarium program + * (c) Kirk Baucom , which you can find at + * http://www.robobunny.com/projects/asciiquarium/ + * + * Ported to KDE by Maksim Orlovich and + * Michael Pyne . + * + * Copyright (c) 2003 Kirk Baucom + * Copyright (c) 2005 Maksim Orlovich + * Copyright (c) 2005 Michael Pyne + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef AA_SPRITE_H +#define AA_SPRITE_H + +#include +#include "frame.h" + +/** + * This class represents a on-screen character of some sort. These make up + * the building blocks of the animation. + * + * You can use multiple frames of animation, but movement is not supported in + * this class, try MovingSprite. If you use multiple frames, use + * setFrameDelay to control the interval between frames, and use + * setDieAfterLastFrame to set whether the animation should loop or cause + * the Sprite to go away. + * + * Use kill() to get rid of a Sprite, do not delete it by yourself, as Screen + * will do that as needed. + */ +class Sprite +{ +protected: + Screen* m_screen; ///< The Screen that we belong to. + int m_currentFrame; ///< The current frame of animation. + int m_x; ///< Our current logical x position. + int m_y; ///< Our current logical y position. + int m_z; ///< Our current depth. + + QValueVector m_frames; ///< Array of animation frames. + bool m_isKilled; ///< True if we've been killed. + bool m_killAfterLastFrame; ///< True if we should auto-kill after the last frame. + int m_ticksSinceFrameChange; ///< Number of timer ticks since we last changed frame. + int m_frameDelay; ///< Number of milliseconds to show a frame for. + + /** + * Increments the animation timer. + * + * @return true if time has elapsed past m_frameDelay since the last frame + * change. + */ + bool timerTick(); + +public: + /** + * Construct a sprite without automatically adding it to \p screen. + * + * @param screen The Screen that the sprite belongs to. + * @param x The x column position for the left edge of this sprite. + * @param y The y row position for the upper line of this sprite. + * @param z The depth of the sprite (0 is closest to screen). + * @param frameDelay Amount of milliseconds to elapse between animation + * frames. + */ + Sprite(Screen* screen, int x, int y, int z, int frameDelay = 100); + + /** + * Destuctor. Does nothing at this point, present to ensure a continuous + * line of virtual destructors. + */ + virtual ~Sprite() + { + } + + /** + * @return true if this sprite can be involved in a collision with another + * Sprite. The other sprite doesn't necessarily have to have this + * also set to true. + */ + virtual bool canCollide() const { return false; } + + /** + * Called when a collision occurs with *any* Sprite on-screen if canCollide() + * returns true. + * + * @param sprite The Sprite that a collision happened with. It is safe to + * kill() the Sprite, move it, etc. + */ + virtual void collision (Sprite *sprite) + { + } + + /** + * Appends a frame of animation to the end of the current list. + * + * @param frame Frame of animation to add. It should be the same size as + * the other frames already in the list. + */ + void addFrame(const Frame& frame); + + /** + * Sets the amount of time to show a frame for. + * + * @param delay The frame delay, in milliseconds of time. + */ + void setFrameDelay(int delay) + { + m_frameDelay = delay; + } + + /** + * Sets whether this Sprite should automatically call kill() after the + * last frame of animation has run. + * + * @param dieAfterLast If true, this Sprite will automatically call kill() + * after its last frame has elapsed. + */ + void setDieAfterLastFrame(bool dieAfterLast) + { + m_killAfterLastFrame = dieAfterLast; + } + + /** + * @return The Screen this Sprite belongs to. + */ + Screen *screen() const + { + return m_screen; + } + + /** + * @return true if this Sprite is dead. If true, it will probably soon be + * deleted by its Screen. + */ + bool isKilled() const + { + return m_isKilled; + } + + /** + * @return The depth of the Sprite. 0 is closest to the screen. + */ + int depth() const + { + return m_z; + } + + /** + * @return The rectangular geometry of this object in the Pixel coordinate + * system. + */ + QRect geom() const; + + /** + * Erases this Sprite from its Screen, using the current animation frame to + * form the clear mask. This should be called *before* any change which + * will change the on-screen display of the object, such as motion or + * animation changes. + */ + void erase(); + + /** + * Draws this Sprite onto the Screen. + */ + void paint(); + + /** + * Kills this Sprite. The parent Screen will delete this Sprite on the next + * animation cycle. + */ + virtual void kill() + { + m_isKilled = true; + } + + //main animation hook. Should return true + erase if something changed + /** + * Called when the current frame expires. This function needs to perform + * any actions necessary to make sure that it is ready to be painted, + * including calling erase(). You do not need to call paint() from this + * function. + * + * @return true if the on-screen representation of this Sprite changed, + * false, otherwise. + */ + virtual bool tickUpdate(); +}; + +#endif + +// vim: set et ts=8 sw=4: